一个pyinstller打包程序的函数级hook小技巧
原理
PyInstaller打包完的CPython运行环境中暴露了几个导出函数
- PyGILState_Ensure
- PyGILState_Release
- PyRun_SimpleStringFlags
- Py_IsInitialized
其中PyRun_SimpleStringFlags支持直接在目标程序上下文中直接运行py脚本,GIL是python解释器的互斥锁,只要当前线程拿到这个锁,就可以在解释器里执行任意的东西
整体流程为frida轮询Py_IsInitializedd确认cpython环境初始化完毕,然后调用PyGILState_Ensure把GIL关联到frida线程,用PyRun_SimpleStringFlags执行想执行的python代码后再通过PyGILState_Releases释放锁
实操
遇到一个神秘的租号软件,输入卡密后从远程服务器拉取cookie注入到定制chrome浏览器中

查看了这个定制浏览器发现把devtool和插件功能都屏蔽了,没法导出cookie到外面用
考虑到cookie注入的过程中肯定有一个阶段是明文cookie,于是让ai分析一下这个程序,居然是纯pyc实现
1 2 3 4 5
| MainWindow._launch_browser_with_cookies -> CookieExtractor.launch_browser(...) -> CookieExtractor.create_context(storage_data=...) -> context.add_cookies(storage_data["cookies"]) -> context.add_init_script(...) # localStorage
|
所以直接把app.cookie_extractor模块里的CookieExtractor.create_context给替换了,然后把cookie解析出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def _install_cookie_hooks(): module = _sys.modules.get("app.cookie_extractor") cls = getattr(module, "CookieExtractor", None) if module is not None else None if cls is None: return
original = getattr(cls, "create_context", None) if original is not None and not getattr(original, "__frida_audit_wrapped__", False): async def create_context(self, storage_data): _emit("cookie_injection", source="login_response_create_context", storage=_storage_summary(storage_data)) return await original(self, storage_data) create_context.__frida_audit_wrapped__ = True cls.create_context = create_context _emit("hook_installed", target="CookieExtractor.create_context")
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function findPythonApi() { const modules = Process.enumerateModules(); for (const module of modules) { const ensure = module.findExportByName('PyGILState_Ensure'); const release = module.findExportByName('PyGILState_Release'); const run = module.findExportByName('PyRun_SimpleStringFlags'); const initialized = module.findExportByName('Py_IsInitialized'); if (ensure && release && run && initialized) { return { module, ensure, release, run, initialized }; } } return null; }
let api = null; let installed = false; let attempts = 0;
function runPython(source) { const gil = new NativeFunction(api.ensure, 'int', [])(); try { const run = new NativeFunction(api.run, 'int', ['pointer', 'pointer']); const code = Memory.allocUtf8String(source); return run(code, ptr(0)); } finally { new NativeFunction(api.release, 'void', ['int'])(gil); } }
const timer = setInterval(function () { attempts += 1; if (api === null) { api = findPythonApi(); if (api !== null) { console.log('[FRIDA-AUDIT] CPython found in ' + api.module.name); } } if (api === null) { return; } const isInitialized = new NativeFunction(api.initialized, 'int', [])(); if (!isInitialized) { return; } const source = installed ? 'import builtins; builtins.__site_login_audit_install()' : PYTHON_AUDIT; const result = runPython(source); if (result === 0) { installed = true; } if (installed && attempts > 120) { clearInterval(timer); } }, 100);
|