ekcbw/pymodhook

GitHub: ekcbw/pymodhook

一个Python模块调用记录与动态分析库,支持运行时hook和DLL注入,用于逆向工程和打包应用的行为追踪。

Stars: 104 | Forks: 14

[English | [中文](README_zh.md)] `pymodhook` 是一个用于记录 Python 模块任意调用的库,旨在用于 Python 逆向工程和分析。它支持包括 Windows、Linux 和 macOS 在内的多种平台。 `pymodhook` 库类似于 Android 的 Xposed 框架,但它不仅记录函数调用的参数和返回值——基于 [pyobject.objproxy](https://github.com/ekcbw/pyobject?tab=readme-ov-file#object-proxy-classes-objchain-and-proxiedobj) 库,它还可以记录模块类的任意方法调用,以及对任何派生对象的访问。 ## 安装 只需运行命令 `pip install pymodhook`。 ## 示例用法 一个 hook `numpy` 和 `matplotlib` 库的示例: ``` from pymodhook import * init_hook() hook_modules("numpy", "matplotlib.pyplot", for_=["__main__"]) # Record calls to numpy and matplotlib enable_hook() import numpy as np import matplotlib.pyplot as plt arr = np.array(range(1,11)) arr_squared = arr ** 2 mean = np.mean(arr) std_dev = np.std(arr) print(mean, std_dev) plt.plot(arr, arr_squared) plt.show() # 显示记录的代码 print(f"Raw call trace:\n{get_code()}\n") print(f"Optimized code:\n{get_optimized_code()}") ``` 运行后,输出将类似于 IDA 等工具生成的结果: ``` Raw call trace: import numpy as np matplotlib = __import__('matplotlib.pyplot') var0 = matplotlib.pyplot var1 = np.array var2 = var1(range(1, 11)) var3 = var2 ** 2 var4 = np.mean var5 = var4(var2) var6 = var2.mean var7 = var6(axis=None, dtype=None, out=None) var8 = np.std var9 = var8(var2) var10 = var2.std var11 = var10(axis=None, dtype=None, out=None, ddof=0) ex_var12 = str(var5) ex_var13 = str(var9) var14 = var0.plot var15 = var14(var2, var3) var16 = var2.shape var17 = var2.shape var18 = var2[(slice(None, None, None), None)] var19 = var18.ndim var20 = var3.shape var21 = var3.shape var22 = var3[(slice(None, None, None), None)] var23 = var22.ndim var24 = var2.values var25 = var2._data var26 = var2.__array_struct__ var27 = var3.values ... var51 = var41.__array_struct__ var52 = var0.show var53 = var52() Optimized code: import numpy as np import matplotlib.pyplot as plt var2 = np.array(range(1, 11)) plt.plot(var2, var2 ** 2) plt.show() ``` ## 详细用法 - `init_hook(export_trivial_obj=True, hook_method_call=False, **kw)` 初始化模块 hooking。在使用 `hook_module()` 或 `hook_modules()` 之前必须调用此方法。 - `export_trivial_obj`:是否*不* hook 模块函数返回的基本类型(如 int、list、dict)。 - `hook_method_call`:是否 hook 模块类实例上的内部方法调用(即 `self` 是 `ProxiedObj` 而不是原始对象的方法)。 - 其他参数通过 `**kw` 传递给 `ObjChain`。 - `hook_module(module_name, for_=None, hook_once=False, deep_hook=False, deep_hook_internal=False, hook_reload=True)` Hook 一个模块,以便后续的 import 将返回被 hook 的版本。 - `module_name`:要 hook 的模块名称(例如 `"numpy"`)。 - `for_`:仅在从特定模块导入时应用 hook(例如 `["__main__"]`),以避免底层模块之间的依赖关系导致错误。如果未指定,则全局应用 hook。 - `hook_once`:仅在第一次导入时返回被 hook 的模块;后续导入返回原始模块。 - `deep_hook`:是否 hook 模块内的每一个函数和类,而不仅仅是模块本身。当 `deep_hook` 为 `True` 时,模块始终被 hook,且 `for_`、`hook_once` 和 `enable_hook` 无效。 - `deep_hook_internal`:如果 `deep_hook` 为 `True`,决定是否 hook 名称以下划线开头的对象(不包括像 `__loader__` 这样的双下划线对象)。 - `hook_reload`:在 `importlib.reload()` 返回新模块后是否仍应用 hooking。 - `hook_modules(*modules, **kw)` 一次性 hook 多个模块,例如 `hook_modules("numpy","matplotlib")`。其他关键字参数与 `hook_module` 中相同。 - `unhook_module(module_name)` Unhook 指定的模块,包括那些使用 `deep_hook` hook 的模块。 - `module_name`:要 unhook 的模块名称。 - `enable_hook()` 启用全局 hook 开关(默认关闭)。只有在启用时,import 才会返回被 hook 的模块。如果 `deep_hook=True` 则不需要。 - `disable_hook()` 禁用全局 hook 开关。禁用时,import 不会返回被 hook 的模块,除非使用了 `deep_hook=True`。 - `import_module(module_name)` 导入并返回子模块对象而不是根模块。 - `module_name`:例如,`"matplotlib.pyplot"` 将返回 `pyplot` 子模块。 - `get_code(*args, **kw)` 生成原始调用追踪的 Python 代码,可用于重建当前的对象依赖关系和使用历史。 - `get_optimized_code(*args, **kw)` 生成优化后的代码,类似于 `get_code`。(代码优化内部使用有向无环图 DAG,详见 [pyobject](https://github.com/ekcbw/pyobject?tab=readme-ov-file#object-proxy-classes-objchain-and-proxiedobj) 库)。 - `get_scope_dump()` 返回 hook 链的变量命名空间(scope)字典的浅拷贝,通常用于调试和分析。 - `dump_scope(file=None)` 使用 `pprint` 将整个变量命名空间字典转储到流 `file` 中。如果对象的 `__repr__()` 方法遇到错误,输出不会被中断。`file` 默认为 `sys.stdout`。 - `getchain()` 返回用于模块 hooking 的全局 `pyobject.ObjChain` 实例,允许手动操作。如果未调用 `init_hook()`,则返回 `None`。 ## 工作原理 在内部,该库使用 `pyobject.objproxy` 库中的 `ObjChain` 类进行动态代码生成。`pymodhook` 本身是 `pyobject.objproxy` 的更高级封装。更多详情请参阅 [pyobject.objproxy 文档](https://github.com/ekcbw/pyobject?tab=readme-ov-file#object-proxy-classes-objchain-and-proxiedobj)。 #### pymodhook-patches 目录 `pymodhook-patches` 目录包含多个以 Python 模块命名的 JSON 文件。这些文件定义了不应被 hook 的自定义属性和函数名称,以确保与特定 Python 库的兼容性。 例如,`matplotlib.pyplot.json` 的结构如下: ``` { // All keys are optional "export_attrs": ["attr"], // Attribute names to export (i.e., `plt.attr` returns the original object instead of a `pyobject.ProxiedObj`) "export_funcs": ["plot", "show"], // Function names to export (i.e., return values remain original objects instead of being wrapped) "alias_name": "plt", // Common module alias (e.g., used for code generation formatting, such as `import matplotlib.pyplot as plt`) "use_proxied_obj":["Figure"] // Functions/classes that require further tracking; if the output code lacks certain calls, this item can be modified (effective only when deep_hook=True). } ``` ## DLL 注入工具的用法 仓库目录 [hook_win32](https://github.com/ekcbw/PyModuleHook/tree/main/tools/hook_win32) 包含一个 DLL 注入工具。由于它仅依赖于已加载的 `python3x.dll`,因此支持记录使用 Nuitka/Cython 打包的应用程序的模块调用,而不仅仅是 PyInstaller。 **注意:请勿使用此工具注入任何未经授权的商业软件!** #### 1. 复制模块文件 首先,使用 `pip install pymodhook` 安装 `pymodhook` 及其依赖 `pyobject`。 然后导航到 `/Lib/site-packages`(Python 安装目录可能因环境而异),复制 `pyobject` 包、`pymodhook.py`、`pymodhook-patches` 目录以及 [\_\_hook\_\_.py](tools/templates/__hook__.py) 到该目录: ![](https://i-blog.csdnimg.cn/direct/c23cec23ff2b41b0a5086d5e12e25ccf.png) 此外,如果使用 Python 3.8 或更早版本,还必须复制 `astor` 模块。 #### 2. 修改 \_\_hook\_\_.py `__hook__.py` 是注入的 DLL 执行的第一段 Python 代码。默认的 `__hook__.py` 如下: ``` # 位于打包程序目录中的 __hook__.py 模板 import atexit, pprint, traceback CODE_FILE = "hook_output.py" OPTIMIZED_CODE_FILE = "optimized_hook_output.py" VAR_DUMP_FILE = "var_dump.txt" ERR_FILE = "hooktool_err.log" def export_code(): try: with open(CODE_FILE, "w", encoding="utf-8") as f: f.write(get_code()) with open(VAR_DUMP_FILE, "w", encoding="utf-8") as f: dump_scope(file=f) with open(OPTIMIZED_CODE_FILE, "w", encoding="utf-8") as f: f.write(get_optimized_code()) except Exception: with open(ERR_FILE, "w", encoding="utf-8") as f: traceback.print_exc(file=f) try: from pymodhook import * from pyobject.objproxy import ReprFormatProxy init_hook() hook_modules("wx","matplotlib.pyplot","requests",deep_hook=True) # This line can be modified by your own atexit.register(export_code) except Exception: with open(ERR_FILE, "w", encoding="utf-8") as f: traceback.print_exc(file=f) ``` 通常,您只需要修改调用 `hook_modules()` 的那一行,以包含其他自定义模块。`deep_hook=True` 选项通常用于使用 Cython/Nuitka 打包的应用程序,对于常规应用程序是可选的。 此外,对于特定的库,您可能需要手动修改 [pymodhook-patches 目录](pymodhook-patches directory)。 #### 3. 注入 DLL 从项目的 [Release](https://github.com/ekcbw/PyModuleHook/releases/latest) 页面下载 `DLLInject_win_amd64.zip`。 下载后,解压并运行 `hook_win32.exe`,搜索目标进程,选中它,然后点击 "Inject DLL" 按钮: ![](https://i-blog.csdnimg.cn/direct/bb07a38301994bbabe40413a623feeed.png) 如果注入成功,您将看到此提示: ![](https://i-blog.csdnimg.cn/direct/1849346064e14ca680daff02b573ffd0.png) #### 4. 获取注入结果 注入成功后,如果程序正常退出(非强制终止),模块 hook 结果——`hook_output.py`、`optimized_hook_output.py` 和 `var_dump.txt`——将在被注入进程的工作目录中生成。 - `hook_output.py` 包含原始的详细调用日志。 - `optimized_hook_output.py` 包含简化的模块调用代码。 - `var_dump.txt` 包含所有变量的转储。 如果结果生成失败,将创建一个额外的文件 `hooktool_err.log` 来记录错误信息。 `optimized_hook_output.py` 示例: ``` import tkinter as tk Canvas = tk.Canvas import matplotlib.pyplot as plt import requests var0 = tk.Tk() ex_var1 = int(tk.wantobjects) var15 = var0.tk var0.title('Tk') var0.withdraw() var0.iconbitmap('paint.ico') var0.geometry('400x300') var0.overrideredirect(ex_var1) var43 = Frame(var0, bg='gray92') var43._last_child_ids = {} var28 = Canvas(var43, bg='#d0d0d0', fg='#000000') var28.pack(expand=ex_var1, fill='x') var28._last_child_ids = {} # external var53: var0.bind('', var53) var0.mainloop() ... ``` `var_dump.txt` 示例: ``` {..., 'ex_var855': True, 'ex_var860': True, 'ex_var875': True, ... 'var123': , 'var124': , 'var125': {'command': >, 'text': 'Save', 'width': 4}, 'var126': None, 'var127': , 'var128': {'command': >, 'text': 'Save', 'width': 4}, ... 'var146': '.!frame.!button3', 'var147': , 'var148': '', 'var152': , 'var153': , 'var154': {'command': >, 'text': 'Clear', 'width': 4}, ... } ``` ## Star 趋势 [![Star History Chart](https://api.star-history.com/svg?repos=ekcbw/pymodhook&type=date&legend=top-left)](https://www.star-history.com/#ekcbw/pymodhook&type=date&legend=top-left)
标签:API监控, Python, Xposed类似, 云资产清单, 代码分析, 凭证管理, 函数追踪, 对象代理, 无后门, 模块注入, 自动化payload嵌入, 调用记录, 逆向工具, 逆向工程