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) 到该目录:

此外,如果使用 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" 按钮:

如果注入成功,您将看到此提示:

#### 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 趋势
[](https://www.star-history.com/#ekcbw/pymodhook&type=date&legend=top-left)
标签:API监控, Python, Xposed类似, 云资产清单, 代码分析, 凭证管理, 函数追踪, 对象代理, 无后门, 模块注入, 自动化payload嵌入, 调用记录, 逆向工具, 逆向工程