1._bootstrap.py
要讲import的过程,就必然离不开_bootstrap.py文件。基本上import的功能都在这个Py脚本中实现。
在Python初始化的代码中,可以找到函数init_importlib。其中调用了一个函数PyImport_ImportFrozenModule。该函数的参数是字符串"_frozen_importlib"。
PyImport_ImportFrozenModule调用其中PyImport_ImportFrozenModuleObject。这里可以看到一个find_frozen函数。
进入find_frozen可以看到look_up_frozen的调用。
在look_up_frozen可看到一个循环字符串匹配。匹配的字符串来自于数组_PyImport_FrozenBootstrap。
查看_PyImport_FrozenBootstrap的定义。
继续查看。可以找到"_frozen_importlib"的定义。其主要内容是GET_CODE(importlib__bootstrap)。
找到_Py_get_importlibbootstrap_toplevel的定义。importlibbootstrap_do_patchups是个空函数,无需在意。
关键是importlibbootstrap_toplevel的定义。查看importlibbootstrap_toplevel的定义。是一个PyCodeObject对象。
其有一个co_code_adaptive成员。该内容就是由_bootstrap.py脚本编译而来。
成功获取_frozen_importlib的数据后,就需要加载其代码。到这里,基本就完成了_frozen_importlib模块的加载。
之后是执行_bootstrap.py中的_install函数(详见_bootstrap.py)。
以上内容在pyinit_core完成。之后在pyinit_main中可看到以下代码。
进入init_interp_main,可看到以下代码。
进入init_importlib_external,可看到以下代码。其中PyObject_CallMethod函数主要功能是执行Python代码的函数(PyCodeObject)。
到_bootstrap.py中,可看到此函数定义。其中有一个import语句。这也是Python开始运行执行的第一个import指令。
2.Python的import如何实现
首先通过dis模块看看import语句是如何执行的。
文本 描述已自动生成
可以看到有一个重要的opcode指令IMPORT_NAME。在Python\ceval.c中可找到处理IMPORT_NAME指令的代码。
其实就是执行了一个import_name函数。import_name中调用PyImport_ImportModuleLevelObject函数。
继续往下走,可看到以下代码。其中abs_name记录了要import的模块名称。
在import_find_and_load可见到下列代码。是执行了_bootstrap.py中的_find_and_load。
之后就是进入Python代码了,_find_and_load的定义如下。
后续加载环节都在_bootstrap.py中。最终module对象直接由PyObject_CallMethodObjArgs返回。
3.扩展编程
基于上述所说,import指令最终会执行到_bootstrap.py的_find_and_load函数中。既然如此,就通过Python代码来实现import的Hook。
准备一个函数,如果导入模块是qwe,就替换成base64。注意,函数要实现_find_and_load的基本功能,否则就无法成功加载模块。
修改_frozen_importlib._find_and_load的code对象,改成我们自己的函数对象。调用import指令。
执行后并没有啥报错。查看sys.modules,发现base64已经被加载。
注意虽然base64模块加载完成了,但是使用的时候符号还是qwe才行。
4.总结
这个方法可以hook很多函数。但需要注意一点,在修改function的code成员地址时,代码的运行空间发生了变化。新code所使用的模块、全局变量等,必须是原本模块所引用或含有的,否则会导致执行报错。
新code可以毫无顾忌的使用原本模块引用的模块、变量、符号等。Python在py->PyCodeObject时,不会进行语法检查。