标题: windbg+pykd入门 创建: 2020-12-10 11:51 更新: 2020-12-23 11:51 链接: https://scz.617.cn/python/202012101151.txt -------------------------------------------------------------------------- 目录: ☆ pykd简介 ☆ 编译pykd源码 9) 修正pykd.pyd处理sys.argv[]的BUG ☆ 编译pykd-ext源码 2) 修改pykd-ext使之支持从pykd子目录加载.py ☆ 安装pykd 1) pip获取pykd.pyd 2) 从pypi.org下载 3) 从githomelab.ru下载(官方发布) 4) pykd-ext 4.1) pykd-ext如何定位Python解释器 5) msdia140.dll的幺蛾子 ☆ pykd vs pykd-ext ☆ 便携版pykd ☆ pykd使用示例 1) 执行windbg命令 2) 读寄存器/内存 3) 获取函数地址 4) 条件断点(bp_sample_0.py) 5) 条件断点(bp_sample_1.py) x) 杂项 ☆ 参考资源 -------------------------------------------------------------------------- ☆ pykd简介 传统windbg插件是用C开发的,jsprovider.dll允许用JavaScript写windbg插件, pykd.pyd则允许用Python写windbg插件,pykd同时支持Python 2.x/3.x。 先统一一下术语: -------------------------------------------------------------- 常用称呼 对应文件 windbg加载方式 必需 支持便携版Python -------------------------------------------------------------- pykd pykd.pyd .load pykd.pyd Y Y pykd-ext pykd.dll .load pykd N N -------------------------------------------------------------- 本文测试用例所涉及的组件如下: x64/windbg 10.0.19041.1 x64/Python 3.9 (便携版) x64/pykd 0.3.4.15 x64/pykd-ext 2.0.0.24 ☆ 编译pykd源码 https://githomelab.ru/pykd/pykd 这个链接上作者说了,安装git、cmake,用Visual Studio 2017打开pykd.sln。这应 该是最简编译方案。没有实际测试,因为没有安装VS 2017,不知VS 2017社区版行不 行,估计是可以的。 -------------------------------------------------------------------------- Visual Studio 2017 社区版 https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/ https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&rel=15 https://cmake.org/ https://cmake.org/download/ https://git-scm.com/ -------------------------------------------------------------------------- 如果有VS 2017,就老老实实用它吧,别瞎折腾其他版本,没必要。git、cmake应该 出现在PATH环境变量中。 我用VS 2019社区版编译成功。 9) 修正pykd.pyd处理sys.argv[]的BUG pykd.pyd处理sys.argv[]的代码有BUG,导致给some.py指定的参数不能稳定传入; 后来调试分析找到BUG源,通过修改pykd.pyd源码解决了BUG。 !pykd.pyd.py sys_argv.py "12345678" !pykd.pyd.py sys_argv.py "1234567" 起初发现上述两条命令有重大差别,前者的sys.argv[1]是"12345678",后者的 sys.argv[1]是""。黑盒测试有个初步结论,当len(sys.argv[i])小于8(i>0)时, some.py看到的sys.argv[i]是空串,后来调试分析表明这只是不可靠的表象结论,换 个测试环境该黑盒测试结论可能就不成立。 不直接用pykd.pyd,换用pykd-ext,如下两条命令看到的sys.argv[1]符合预期: !pykd.py sys_argv.py "12345678" !pykd.py sys_argv.py "1234567" 参看: pykd\pykd\windbgext.cpp -------------------------------------------------------------------------- KDLIB_EXT_COMMAND_METHOD_IMPL(PykdExt, py) { ... /* * 取some.py全路径 */ std::string scriptFileName = getScriptFileName(args[0]); #if PY_VERSION_HEX >= 0x03000000 // wchar_t **pythonArgs = new wchar_t* [ args.size() ]; std::wstring scriptFileNameW = _bstr_t(scriptFileName.c_str()); pythonArgs[0] = const_cast(scriptFileNameW.c_str()); for (size_t i = 1; i < args.size(); ++i) { /* * argw的作用域仅限于这个for循环 */ std::wstring argw = _bstr_t(args[i].c_str()); /* * pythonArgs[i]引用的对象在离开for循环后将被析构 */ pythonArgs[i] = const_cast(argw.c_str()); } /* * 此处的pythonArgs[]引用了已被释放的内存残像 */ PySys_SetArgv( (int)args.size(), pythonArgs ); delete[] pythonArgs; #else -------------------------------------------------------------------------- 注释中已指明BUG源头所在,变量作用域相关。最初审看此段代码并未意识到BUG,只 好编译Debug版pykd.pyd,动态调试发现给pythonArgs[i]赋值时sys.argv[i]还是正 常的,但断在python39!PySys_SetArgv()时,pythonArgs[i]已经变成空串或其他乱 七八糟的内容,打算用数据断点看到底谁改变了pythonArgs[i]。在IDA中查看附近的 汇编代码时看到for循环尾部有调用std::wstring::~wstring(),才意识到内存释放 后继续使用的BUG。下面是一个简单修正: -------------------------------------------------------------------------- KDLIB_EXT_COMMAND_METHOD_IMPL(PykdExt, py) { ... /* * 取some.py全路径 */ std::string scriptFileName = getScriptFileName(args[0]); #if PY_VERSION_HEX >= 0x03000000 // wchar_t **pythonArgs = new wchar_t* [ args.size() ]; std::wstring scriptFileNameW = _bstr_t(scriptFileName.c_str()); pythonArgs[0] = const_cast(scriptFileNameW.c_str()); std::vector argws(args.size()); for (size_t i = 1; i < args.size(); ++i) { argws[i] = _bstr_t(args[i].c_str()); pythonArgs[i] = const_cast(argws[i].c_str()); // printf("[%ls]\n", pythonArgs[i]); } PySys_SetArgv( (int)args.size(), pythonArgs ); delete[] pythonArgs; #else -------------------------------------------------------------------------- pykd作者推荐通过pykd-ext使用pykd,而pykd-ext处理sys.argv[]的代码无此BUG, 结果pykd的这个BUG一直隐藏至今,太坑了。 ☆ 编译pykd-ext源码 $ git clone https://githomelab.ru/pykd/pykd-ext.git pykd-ext 用VS 2019社区版打开pykd_ext.sln,Build,自动下载依赖源码,比如: pykd-ext\packages\ pykd-ext\packages\boost.1.67.0.0\ 相比编译pykd,编译pykd-ext完全没幺蛾子,直接成功。 pykd-ext\out\x64\Release\pykd.dll pykd-ext\out\x64\Release\pykd_ext_2.0.pdb 2) 修改pykd-ext使之支持从pykd子目录加载.py "!pykd.pyd.py"找命令行上的some.py时认python39._pth中的路径,可以不指定目录 名,只指定文件名。"!pykd.py"找命令行上的some.py时不认python39._pth中的路径。 先看pykd.pyd为什么认python39._pth中的路径,参看: pykd\pykd\windbgext.h pykd\pykd\windbgext.cpp -------------------------------------------------------------------------- void PykdExt::setUp() { ... Py_Initialize(); ... python::object sys = python::import("sys"); ... /* * 取sys.path */ python::list pathList = python::extract(sys.attr("path")); python::ssize_t n = python::len(pathList); for (python::ssize_t i = 0; i < n ; i++) /* * 成员变量m_paths存放sys.path */ m_paths.push_back(boost::python::extract(pathList[i])); ... } -------------------------------------------------------------------------- /* * 该函数用于定位some.py */ std::string PykdExt::getScriptFileName( const std::string &scriptName ) { std::string scriptFileName = findScript( scriptName ); if ( scriptFileName.empty() ) { std::string scriptNameLow; scriptNameLow.resize( scriptName.size() ); std::transform( scriptName.begin(), scriptName.end(), scriptNameLow.begin(), ::tolower); if ( scriptNameLow.rfind(".py") != (scriptNameLow.length() - 3) ) scriptFileName = findScript( scriptName + ".py" ); } return scriptFileName; } -------------------------------------------------------------------------- /* * 该函数用于定位some.py */ std::string PykdExt::findScript( const std::string &fullFileName ) { if ( GetFileAttributesA(fullFileName.c_str()) != INVALID_FILE_ATTRIBUTES ) return fullFileName; /* * 如果指定相对路径,在sys.path中依次寻找some.py */ std::vector::const_iterator it = m_paths.begin(); for ( ; it != m_paths.end(); ++it) { DWORD bufSize = SearchPathA( -------------------------------------------------------------------------- pykd.pyd定位some.py的代码逻辑认可sys.path的设置,所以认python39._pth中的路 径。 pykd-ext同样有个getScriptFileName()用于定位some.py,参看: pykd-ext\sources\windbgext.cpp -------------------------------------------------------------------------- /* * 该函数用于定位some.py */ std::string getScriptFileName(const std::string &scriptName) { char* ext = ".py"; DWORD searchResult = SearchPathA( NULL, scriptName.c_str(), ext, 0, NULL, NULL); if ( searchResult == 0 ) { return ""; } std::vector pathBuffer(searchResult); searchResult = SearchPathA( NULL, scriptName.c_str(), ext, pathBuffer.size(), &pathBuffer.front(), NULL ); return std::string(&pathBuffer.front(), searchResult); } -------------------------------------------------------------------------- pykd-ext的getScriptFileName()没有用到sys.path,故不认python39._pth中的路径。 让pykd-ext支持sys.path的话,动静太大,但可以小改使之从pykd.dll所在目录的 pykd子目录中寻找some.py。 -------------------------------------------------------------------------- /* * added by scz */ static int PrivateGetCurrentModulePath ( char *path, int pathlen ) { HMODULE hm = NULL; if ( GetModuleHandleExA ( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&PrivateGetCurrentModulePath, &hm ) == 0 ) { int ret = GetLastError(); fprintf( stderr, "GetModuleHandleEx() failed, error = %d\n", ret ); return( -1 ); } if ( 0 == GetModuleFileNameA( hm, path, pathlen ) ) { int ret = GetLastError(); fprintf( stderr, "GetModuleFileName() failed, error = %d\n", ret ); return( -1 ); } char *p = strrchr( path, '\\' ); if ( p != NULL ) { *p = '\0'; } return( 0 ); } /* end of PrivateGetCurrentModulePath */ /* * added by scz */ std::string getScriptFileName2(const std::string &scriptName) { char path[MAX_PATH]; if ( 0 == PrivateGetCurrentModulePath( path, sizeof( path ) ) ) { // printf( "%s\n", path ); char *infix = "\\pykd\\"; char *suffix = (char*)scriptName.c_str(); if ( strlen( path ) + strlen( infix ) + strlen( suffix ) + 1 <= MAX_PATH ) { std::stringstream pypath; pypath << path << infix << suffix; char *pypath2 = (char*)pypath.str().c_str(); // printf( "%s\n", pypath2 ); char* ext = ".py"; DWORD searchResult = SearchPathA( NULL, pypath2, ext, 0, NULL, NULL); if ( searchResult == 0 ) { return ""; } std::vector pathBuffer(searchResult); searchResult = SearchPathA( NULL, pypath2, ext, pathBuffer.size(), &pathBuffer.front(), NULL ); return std::string(&pathBuffer.front(), searchResult); } } return ""; } /* end of getScriptFileName2 */ /* * modified by scz */ std::string getScriptFileName(const std::string &scriptName) { char* ext = ".py"; DWORD searchResult = SearchPathA( NULL, scriptName.c_str(), ext, 0, NULL, NULL); if ( searchResult == 0 ) { // return ""; return getScriptFileName2( scriptName ); } ... -------------------------------------------------------------------------- 简单改一下,对付着用。 ☆ 安装pykd 网上有很多pykd的安装介绍,很容易绕晕。主要是这个文件,pykd.pyd,不管你用什 么办法搞到这个文件,搞到后放到windbg的winext目录,就算安装完成。 1) pip获取pykd.pyd python.exe -m pip install pykd --upgrade python.exe -m pip list python.exe -m pip show pykd 这样拖回来的pykd.pyd位于: \Lib\site-packages\pykd\pykd.pyd 2) 从pypi.org下载 https://pypi.org/project/pykd/#files https://files.pythonhosted.org/packages/12/2d/fabb94c8bdbfc1748da0f21867ed44eb12a6b016bfe87abe5872ba75d6a3/pykd-0.3.4.15-cp39-none-win_amd64.whl 这里有一堆.whl文件,需要找对应版本,上例是"x64+Python 3.9+0.3.4.15 pykd"。 用7-Zip打开.whl文件,从中析取pykd.pyd。此法本质上同"pip install"。 3) 从githomelab.ru下载(官方发布) https://githomelab.ru/pykd/pykd/-/wikis/home https://githomelab.ru/pykd/pykd/-/wikis/All%20Releases https://githomelab.ru/pykd/pykd/-/wikis/0.3.4.15 Browse 0.3.4.15 dir https://yadi.sk/d/Ia71qisldISyyw?w=1 这里提供pykd-0.3.4.15-cp39-win-amd64.zip下载 https://yadi.sk/d/WLrzu_psR_n40Q 这里提供pykd-0.3.4.15-symbols.zip下载,就是各个版本的.pdb文件。 从.zip中析取pykd.pyd。 4) pykd-ext https://githomelab.ru/pykd/pykd-ext https://githomelab.ru/pykd/pykd-ext/-/wikis/Downloads https://githomelab.ru/pykd/pykd-ext/-/wikis/uploads/0bc82100609d24a8fcd1604203e782a6/pykd_ext_2.0.0.24.zip https://githomelab.ru/pykd/pykd-ext/-/blob/master/README.md pykd_ext_2.0.0.24.zip中有个pykd.dll,把它放到windbg的winext目录。这也是一 个windbg插件,但它不是pykd本身,这是pykd-ext。 pykd-ext不是pykd,没有pykd-ext,照样用pykd。 pykd-ext的使用场景是,假设已安装各种版本的Python,不是便携版,是写过注册表 的安装版。 .load pykd !pykd.help !pykd.info !pykd.select -3 !pykd.py !pykd.py -2 !pykd.py -3 !pykd.py -3.9 some.py arg1 arg2 !pykd.pip -3.9 install --upgrade pykd !pykd.pip -3.9 list !pykd.pip -3.9 show pykd pykd-ext使用细节参看README.md。这就是个安装版Python的Wrapper,没啥特别。 4.1) pykd-ext如何定位Python解释器 pykd-ext通过注册表定位安装版Python,如果你用Python 3.9官方便携版,pykd-ext 看不到它。假设winext目录含有便携版Python,如果非要让pykd-ext找到它,可以临 时添加注册表项: reg.exe add "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" /t REG_SZ /d "...\\" reg.exe query "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" reg.exe delete "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" /f reg.exe delete "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /f reg.exe delete "HKCU\SOFTWARE\Python" /f pykd-ext只找python39.dll,不找python3.dll、python.exe。 可以修改pykd-ext源码,使之在检查注册表之前优先尝试加载pykd.dll所在目录中的 python39.dll,从而直接支持便携版Python。 5) msdia140.dll的幺蛾子 为了使用PDB中的未导出符号,依赖msdia140.dll。有两种方式找到msdia140.dll, 一种是向系统注册之,另一种是将之与pykd.pyd置于同一目录。下列命令需要在管理 员级cmd中执行: 注册 regsvr32.exe msdia140.dll 反注册 regsvr32.exe /u msdia140.dll 若不在管理员级cmd中执行上述命令,会失败报错。用注册法时,msdia140.dll可在 任意目录,注册后注册表中有相关项出现。网上有些介绍pykd的文章说得好像非注册 msdia140.dll不可,完全不那么回事。不推荐注册法,事实上只要msdia140.dll和 pykd.pyd位于同一目录即可,此时无需注册。假设未注册,而msdia140.dll位于 "C:\Windows\System32"目录,这种无效,msdia140.dll必须在pykd.pyd所在目录。 是否注册msdia140.dll,与是否通过pykd-ext使用pykd无关。 若找不到msdia140.dll,也能用pykd,但不能获取PDB中的非导出符号,使得pykd大 大受限。测试用例: "\cdb.exe" -noinh -snul -hd -o -xe ld:ntdll "C:\Windows\System32\notepad.exe" .load pykd !pykd.py import pykd;hex(pykd.getOffset("ntdll!ZwOpenProcessToken")) .load pykd.pyd !pykd.pyd.py import pykd;hex(pykd.getOffset("ntdll!ZwOpenProcessToken")) 若找不到msdia140.dll,抛异常: pykd.SymbolException: ZwOpenProcessToken symbol is not found 但"x ntdll!ZwOpenProcessToken"有输出。 msdia140.dll不是OS自带的,pykd自带,VS 2019也有。 -------------------------------------------------------------------------- 14.13.26020.0 (from pykd) msdia140.dll 14.26.28619.0 C:\Program Files\dotnet\sdk\5.0.101\TestHost\x64\msdia140.dll 14.28.29333.0 (from VS 2019) C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\DIA SDK\bin\amd64\msdia140.dll C:\Windows\System32\msvcp140.dll C:\Windows\System32\msvcp140_atomic_wait.dll -------------------------------------------------------------------------- 前两个只依赖kernel32.dll,第三个还依赖msvcp140.dll、msvcp140_atomic_wait.dll, 如果用第三个,必须与msvcp140.dll、msvcp140_atomic_wait.dll放在一起。一般OS 有msvcp140.dll,但很可能没有msvcp140_atomic_wait.dll。 ☆ pykd vs pykd-ext 无需".load pykd.pyd",可以直接"!pykd.pyd.py"。无需".load pykd",可以直接 "!pykd.py"。 不要混用"!pykd.pyd.py"、"!pykd.py",否则要么cdb.exe失去响应,只能杀掉 cdb.exe进程,要么cdb.exe直接结束。 "!pykd.pyd.py"找命令行上的some.py时认python39._pth中的路径,可以不指定目录 名,只指定文件名。"!pykd.py"找命令行上的some.py时不认python39._pth中的路径。 但pykd.pyd处理sys.argv[]的代码有BUG,导致给some.py指定的参数不能稳定传入。 官方推荐通过pykd-ext用pykd。我不推荐pykd-ext,它依赖注册表,不直接支持便携 版Python,无端增加系统耦合性。 这两种用法真是各有各的扯淡,后来修改各自源码解决前述问题。 ☆ 便携版pykd windbg、Python、pykd都可以达到便携版效果: winext | | pykd.dll // 无所谓 | pykd_ext_2.0.pdb // 不需要 | pykd.pdb // 不需要 | pykd.pyd // 必需 | msdia140.dll // 建议 | msvcp140.dll // 建议 | msvcp140_atomic_wait.dll // 建议 | python39.dll | python39.zip | python39._pth | +---Lib | \---site-packages | hexdump.py | \---pykd sys_argv.py // 自己写的脚本 python39._pth内容示例: -------------------------------------------------------------------------- python39.zip . Lib/site-packages pykd # Uncomment to run site.main() automatically import site -------------------------------------------------------------------------- ☆ pykd使用示例 "\cdb.exe" -noinh -snul -hd -o -xe ld:ntdll "C:\Windows\System32\notepad.exe" .prompt_allow +reg +ea +dis;rm 0xa .load pykd.pyd !pykd.pyd.py !pykd.pyd.py some.py arg1 arg2 !pykd.pyd.py --local some.py arg1 arg2 !pykd.pyd.py --global some.py arg1 arg2 1) 执行windbg命令 !pykd.pyd.py 这会开一个Python提示符,在其中执行: -------------------------------------------------------------------------- import pykd print( pykd.dbgCommand("r") ) -------------------------------------------------------------------------- 这相当于在windbg提示符中执行"r"。关于dbgCommand()这种API,参[2]。 假设some.py内容如下: -------------------------------------------------------------------------- import pykd import hexdump buf = pykd.dbgCommand("!teb").encode( 'latin-1' ) hexdump.hexdump( buf ) print( pykd.dbgCommand("!teb") ) -------------------------------------------------------------------------- !pykd.pyd.py --local some.py encode()的目的是str转bytes。 2) 读寄存器/内存 -------------------------------------------------------------------------- import pykd import hexdump addr = pykd.reg("rip") buf = bytes( pykd.loadBytes(addr,64) ) hexdump.hexdump( buf ) -------------------------------------------------------------------------- 相当于"db @rip l 0x40" 3) 获取函数地址 -------------------------------------------------------------------------- import pykd addr = pykd.getOffset("KERNEL32!CreateFileW") print( hex(addr) ) -------------------------------------------------------------------------- 相当于"x KERNEL32!CreateFileW"。 若找不到msdia140.dll,pykd.getOffset("KERNELBASE!CreateFileInternal")抛异 常,找不到符号,因为这是PDB中的未导出符号;而"KERNEL32!CreateFileW"是导出 符号,不依赖msdia140.dll。 4) 条件断点(bp_sample_0.py) -------------------------------------------------------------------------- # -*- encoding: cp936 -*- # # python3 # # !pykd.pyd.py --global bp_sample_0.py "KERNELBASE!CreateFileInternal" system.ini 1 1 # !pykd.py -g bp_sample_0.py "KERNELBASE!CreateFileInternal" system.ini 0 0 # # # Author : scz@nsfocus # Create : 2020-12-10 11:51 # Modify : # import sys, re, os import pykd # ########################################################################## # def __PrivateLog ( msg ) : sys.stdout.write( msg ) # # end of __PrivateLog # # ########################################################################## # def __EndWithPattern ( sth, pattern, ignorecase=False, debug=False ) : if ( ignorecase ) : s = sth.lower() p = pattern.lower() else : s = sth p = pattern if ( s.endswith( p ) ) : ret = True __PrivateLog( "Hit:[" + sth + "]\n" ) else : if ( debug ) : __PrivateLog( "[" + sth + "]\n" ) ret = False return( ret ) # # end of __EndWithPattern # # ########################################################################## # address = None pattern = None ignorecase = False debug = False bp = None # # breakpoint handler # # By returning False (or nothing) we let the application continue running, # if you return True then execution will stop. # def SomeFunc () : rcx = pykd.reg( "rcx" ) # # filename/filepath # sth = pykd.loadWStr( rcx ) if ( __EndWithPattern( sth, pattern, ignorecase, debug ) ) : return( True ) else : return( False ) # # end of SomeFunc # def main ( prog, args ) : global address, pattern, ignorecase, debug global bp argc = len( args ) if ( argc < 2 ) : sys.stderr.write \ ( 'Usage: %s
[ignorecase] [debug]\n' % prog ) raise SystemExit # # "KERNELBASE!CreateFileInternal" # address = args[0] pattern = args[1] if ( argc > 2 ) : ignorecase = int( args[2], 0 ) if ( argc > 3 ) : debug = int( args[3], 0 ) bpaddr = pykd.getOffset( address ) # # 必须持有一个全局引用,避免离开作用域时被析构,否则Ctrl-Break之后g无 # 法恢复正常状态,断点失效,只有个让你摸不着头脑的提示: # # 80010117 调用结束后,无法访问调用上下文。 # bp = pykd.setBp( bpaddr, SomeFunc ) # # continue execution. # # If you want to break into windbg, you must use Ctrl-Break/Ctrl-Fn-B # instead of Ctrl-C # pykd.go() # # end of main # if __name__ == '__main__' : try : main( os.path.basename( sys.argv[0] ), sys.argv[1:] ) except KeyboardInterrupt : pass -------------------------------------------------------------------------- 这是普通bp断点,要求模块已经加载。 5) 条件断点(bp_sample_1.py) -------------------------------------------------------------------------- # -*- encoding: cp936 -*- # # python3 # # !pykd.pyd.py --global bp_sample_1.py KERNELBASE CreateFileInternal system.ini 1 1 # !pykd.py -g bp_sample_1.py KERNELBASE CreateFileInternal system.ini 0 0 # # # Author : scz@nsfocus # Create : 2020-12-10 11:51 # Modify : # import sys, re, os import pykd # ########################################################################## # def PrivateLog ( msg ) : sys.stdout.write( msg ) # # end of PrivateLog # # ########################################################################## # def EndWithPattern ( sth, pattern, ignorecase=False, debug=False ) : if ( ignorecase ) : s = sth.lower() p = pattern.lower() else : s = sth p = pattern if ( s.endswith( p ) ) : ret = True PrivateLog( "Hit:[" + sth + "]\n" ) else : if ( debug ) : PrivateLog( "[" + sth + "]\n" ) ret = False return( ret ) # # end of EndWithPattern # # ########################################################################## # class PrivateEventHandler ( pykd.eventHandler ) : def __init__ ( self, module_name, module_func, pattern, ignorecase=False, debug=False ) : # # pykd.eventHandler.__init__( self ) # super( PrivateEventHandler, self ).__init__() self.module_name = module_name self.module_func = module_func self.pattern = pattern self.ignorecase = ignorecase self.debug = debug self.module_func_bp = None pykd.go() # # end of __init__ # # # 相当于bu断点 # def onLoadModule ( self, base, name ) : # # PrivateLog( "onLoadModule [" + name + "]\n" ) # if name == self.module_name : module = pykd.module( name ) module.reload() bpaddr = module.offset( module_func ) # # self.module_func_bp = pykd.setBp( bpaddr, self.SomeFunc ) # self.module_func_bp = pykd.setBp( bpaddr ) # # PrivateLog( "[Set breakpoint at %#x]\n" % bpaddr ) # PrivateLog( "[Set breakpoint at %#x]\n" % self.module_func_bp.getOffset() ) # return( pykd.eventResult.NoChange ) # # end of onLoadModule # # # 本例可以不要该方法 # def onUnloadModule ( self, base, name ) : # # PrivateLog( "onUnloadModule [" + name + "]\n" ) # if name == self.module_name : # # PrivateLog( "[Remove breakpoint at %#x]\n" % self.module_func_bp.getOffset() ) # self.module_func_bp.remove() self.module_func_bp = None return( pykd.eventResult.NoChange ) # # end of onUnloadModule # def onBreakpoint ( self, id ) : # # PrivateLog( "[bpid = %#x]\n" % id ) # return( self.SomeFunc() ) # # end of onBreakpoint # def SomeFunc ( self ) : rcx = pykd.reg( "rcx" ) # # filename/filepath # sth = pykd.loadWStr( rcx ) if ( EndWithPattern( sth, self.pattern, self.ignorecase, self.debug ) ) : return( True ) else : return( False ) # # end of SomeFunc # # # end of PrivateEventHandler # # ########################################################################## # module_name = None module_func = None pattern = None ignorecase = False debug = False peh = None def main ( prog, args ) : global module_name, module_func, pattern, ignorecase, debug global peh argc = len( args ) if ( argc < 3 ) : sys.stderr.write \ ( 'Usage: %s [ignorecase] [debug]\n' % prog ) raise SystemExit module_name = args[0] module_func = args[1] pattern = args[2] if ( argc > 3 ) : ignorecase = int( args[3], 0 ) if ( argc > 4 ) : debug = int( args[4], 0 ) peh = PrivateEventHandler( module_name, module_func, pattern, ignorecase, debug ) pykd.go() # # end of main # if __name__ == '__main__' : try : main( os.path.basename( sys.argv[0] ), sys.argv[1:] ) except KeyboardInterrupt : pass -------------------------------------------------------------------------- 这相当于bu断点,允许模块尚未加载。 x) 杂项 一旦pykd.go(),Ctrl-C断不下来,得用Ctrl-Break断。T14笔记本,Ctrl-Fn-B相当于 Ctrl-Break。 在.py中设置的断点只在.py中可见,在cdb提示符下不可见。 在.py中设置的断点只能通过.py去删除?有什么办法彻底消除.py的效果吗? ☆ 参考资源 [1] PyKD Tutorial - Sina Karvandi [2018-05-25] https://rayanfam.com/topics/pykd-tutorial-part1/ https://rayanfam.com/topics/pykd-tutorial-part2/ [2] pykd API Reference https://githomelab.ru/pykd/pykd/-/wikis/API%20Reference pydk User Manual (俄文版) https://githomelab.ru/pykd/pykd/-/wikis/User%20Manual%20rus