标题: MSDN系列(47)--Lighthouse/DynamoRIO/Coverage Diff入门 创建: 2022-02-10 12:30 更新: 2023-07-19 13:32 链接: https://scz.617.cn/windows/202202101230.txt -------------------------------------------------------------------------- 目录: ☆ 原始需求 ☆ Lighthouse 1) 安装 2) drcov.py 3) 加载.log文件 4) test.c 4.1) 生成两组.log 4.2) 用Lighthouse求"B-(B&A)" 5) notepad示例 6) Win7 calc示例 ☆ DynamoRIO 1) .log文件格式 2) 文本格式的.log文件 3) DRCOV VERSION 2 vs 3 4) 无法捕捉Win10 Store App的数据 4.1) 讨论 4.1.3) ForceRelocateImages(终级原因) ☆ 参考资源 -------------------------------------------------------------------------- ☆ 原始需求 Win10的calc与Win7的calc不一样,前者是个Store App,对应Calculator.exe。 启动Win10的calc,切换到"程序员模式",切换到16进制模式,依次输入 51201314 * 41414141 = 得到乘法运算结果 0x14add2aaaa10ec14 原始需求是,寻找前述算术运算相关代码。 参看 《MSDN系列(46)--WinDbg Preview TTD入门》 https://scz.617.cn/windows/202201251528.txt 上篇我用TTD技术找到了前述算术运算对应的代码逻辑。同时bluerust指出,可以试 试"Coverage Diff"技术解决原始需求,他推荐了几种不同的工具,所以就有了本篇。 后来实践了一番"Coverage Diff"技术。但具体到Win10的calc,没能用DynamoRIO成 功捕捉Calculator的执行流,我换了些其他目标PE。 ☆ Lighthouse 1) 安装 参[1] WSL1中 git clone https://github.com/gaasedelen/lighthouse.git lighthouse IDAPython脚本如果用到Qt,得关注一下 IDA\python\3\PyQt5\sip.pyd 该文件随Python版本不同而不同。比如我用3.9,sip.pyd就得取自 IDA\python\3\PyQt5\python_3.9\sip.pyd 用CFF Explorer看sip.pyd的依赖库,依赖python39.dll。若sip.pyd版本不匹配, 用到Qt的IDAPython脚本就会失败。当有try/except时,问题被掩盖,可以临时删除 try/except,让问题曝露出来。测试Lighthouse时意外发现findrpc_scz.py不工作, 由此注意到sip.pyd的坑。 Lighthouse文档中说这样安装 a) import idaapi,os;print(os.path.join(idaapi.get_user_idadir(),"plugins")) b) xcopy lighthouse\plugins "C:\Users\scz\AppData\Roaming\Hex-Rays\IDA Pro\plugins" /e /q 但可以不用"C:\Users\scz\AppData\Roaming\Hex-Rays\IDA Pro\plugins",就用 "X:\Green\IDA\plugins",实测无误。 2) drcov.py X:\Green\IDA\plugins\lighthouse\reader\parsers\drcov.py Lighthouse只支持"DRCOV VERSION: 2",_parse_drcov_header函数中有一句 assert self.version == 2, "Only drcov version 2 log files supported" 如果遭遇版本3的.log文件,Lighthouse会报错,但上面这行提示信息并未显示出来, 非常坑爹,我是用其他手段排查至此的。 -------------------------------------------------------------------------- Error: PARSE_FAILURE Failed to parse one or more of the selected coverage files! Possible reasons: - You selected a file that was *not* a coverage file. - The selected coverage file is malformed or unreadable. - A suitable parser for the coverage file is not installed. Please see the disassembler console for more info... -------------------------------------------------------------------------- 解决办法有两种,一种是修改drcov.py,注释掉assert这行代码,至少DynamoRIO 9.0.1生成的.log文件就可以加载了;另一种是不改drcov.py,用WinHex修改.log的 头部,把"DRCOV VERSION: 3"的3改成2。 3) 加载.log文件 在IDA中 -------------------------------------------------------------------------- File Load file Code coverage file 选中.log文件 -------------------------------------------------------------------------- 若未找到"Code coverage file"菜单项,表明Lighthouse插件加载失败,先检查一下 sip.pyd版本是否匹配。 4) test.c -------------------------------------------------------------------------- /* * cl test.c /Fetest.exe /nologo /Os /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE * cl test.c /Fetest.exe /Zi /Fatest.asm /Fdtest.pdb /nologo /Os /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE /opt:ref */ #include #include int math ( int a, int b ) { if ( a > b ) { return( a - b ); } else { return( a + b ); } } int main ( int argc, char * argv[] ) { int a, b, c; if ( argc >= 3 ) { a = ( int )strtol( argv[1], NULL, 0 ); b = ( int )strtol( argv[2], NULL, 0 ); c = math( a, b ); printf( "%d\n", c ); } return 0; } -------------------------------------------------------------------------- 4.1) 生成两组.log X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- test 1 2 X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- test 3 1 4.2) 用Lighthouse求"B-(B&A)" 在IDA中用Lighthouse加载两组.log,在Composer中输入"B-(B&A)",很容易定位到 -------------------------------------------------------------------------- sub_140001000(int a1, int a2) { if ( a1 <= a2 ) return (unsigned int)(a2 + a1); else return (unsigned int)(a1 - a2); } -------------------------------------------------------------------------- 在Lighthouse创建的"Coverage Overview"窗口中,可以对"Cov %"列排序。不必关注 该值为0的行,在"B-(B&A)"语境下这种属于"常规"代码。该值为100的行属于B特有的 代码块,也不必太关注。重点关注非0、非100的行,很可能涉及流程分叉。 sub_140001000的"Cov %"是31.25,其他函数该值为0。然后靠颜色直接定位第二个 return语句。 本例求"B-A"即可,不必求"B-(B&A)"。更多逻辑表达式参看 https://github.com/gaasedelen/lighthouse 假设有a.log、b.log,对二者进行Coverage Diff。有个坑要注意,可能你想找的代 码在二者中都有经过,此时B-A会排除掉目标地址,在B-A中永远找不到目标地址。当 两个log的区间并不宽广时,不容易掉进这个坑。当两个log很宽广而目标地址位于某 个公共函数中时,很容易掉进这个坑,处理Win10 Calculator时我就掉进去了,后来 才整明白发生了什么。 5) notepad示例 在Win10上测试 X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- notepad 测两次。第一次打开"格式",但不点击"字体",然后就退出。第二次,打开"格式"、 点击"字体"。然后用Lighthouse求"B-(B&A)",得到 -------------------------------------------------------------------------- 100.00 __imp_load_ChooseFontW 36.32 SaveGlobals(void) 10.18 NPCommand(HWND__ *,unsigned __int64,__int64) -------------------------------------------------------------------------- F5查看NPCommand,直接就在这附近 notepad!NPCommand+0x9ee: 00007ff6`9098aa86 e869b90100 call notepad!memset (00007ff6`909a63f4) 与之前用TTD技术定位的一致 6) Win7 calc示例 在Win10上DynamoRIO无法捕捉Calculator的数据。退而求其次,从Win7复制calc到 Win10,用calc做演示。 X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- X:\Green\GUI\calc 打开calc,切换到程序员模式、16进制模式。第一次测试,输入"51201314*",退出。 第二次测试,输入"51201314*41414141=",退出。 用Lighthouse求"B-(B&A)",得到 -------------------------------------------------------------------------- 83.33 CHistoryCollector::CompleteHistoryLine(ushort const *) 9.91 CCalculatorSQM::turnOnProgModeBits(unsigned __int64) 6.61 CCalcEngine::ProcessCommandWorker(unsigned __int64) 5.47 CContainer::ProcessKeyPadInputs(uint,unsigned __int64,__int64) 5.22 CCalcEngine::DoOperation(int,_rat * *,_rat *) 4.88 CCalcEngine::CheckAndAddLastBinOpToHistory(void) 3.74 remnum(_number * *,_number *,long) 2.27 _addnum(_number * *,_number *,ulong) 1.92 IsNumberInvalid(ushort *,unsigned __int64,int,int) -------------------------------------------------------------------------- F5查看CCalcEngine::DoOperation,注意到 calc!CCalcEngine::DoOperation+0x5f9: 00007ff6`0aa26f50 e857e5ffff call calc!mulrat (00007ff6`0aa254ac) 另开一个calc,在此设断,命中后单步跟踪几下,直至 rax=0000000041414141 rbx=0000000000000000 rcx=0000000000000000 rdx=0000000000000001 rsi=00000000012def30 rdi=0000000041414141 rip=00007ff60aa25298 rsp=0000000000f9bc00 rbp=00000000012df0f0 r8=0000000000000001 r9=0000000051201314 r10=00000000012dec5c r11=00000000012def3c r12=00000000012dec50 r13=00000000012ef470 r14=00000000012def40 r15=0000000000000001 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 calc!_mulnumx+0xa2: 00007ff6`0aa25298 4c0fafcf imul r9,rdi 该指令做乘法运算,两个乘数分别是0x51201314、0x41414141。调用栈回溯如下 > kpn # Child-SP RetAddr Call Site 00 00000000`00f9bc00 00007ff6`0aa251d0 calc!_mulnumx+0xa2 01 00000000`00f9bc50 00007ff6`0aa254da calc!mulnumx+0x92 02 00000000`00f9bc80 00007ff6`0aa26f55 calc!mulrat+0x2a 03 00000000`00f9bcb0 00007ff6`0aa26967 calc!CCalcEngine::DoOperation+0x5fe 04 00000000`00f9bd30 00007ff6`0aa249c6 calc!CCalcEngine::ProcessCommandWorker+0x119c 05 00000000`00f9bea0 00007ff6`0aa24938 calc!CCalcEngine::ProcessCommand+0x2a 06 00000000`00f9bed0 00007ff6`0aa2460a calc!CUIController::ProcessKeypadInput+0xaa 07 00000000`00f9bf10 00007ff6`0aa24744 calc!CContainer::ProcessKeyPadInputs+0x7a1 08 00000000`00f9c070 00007ff8`6296e858 calc!WndProc+0xa12 09 00000000`00f9c550 00007ff8`6296de1b USER32!UserCallWinProcCheckWow+0x2f8 0a 00000000`00f9c6e0 00007ff8`6296d68a USER32!SendMessageWorker+0x70b 0b 00000000`00f9c780 00007ff6`0aa30a60 USER32!SendMessageW+0xda 0c 00000000`00f9c7d0 00007ff8`62972920 calc!CProgrammerMode::ProgDlgProc+0x157 0d 00000000`00f9c840 00007ff8`629720c2 USER32!UserCallDlgProcCheckWow+0x144 0e 00000000`00f9c920 00007ff8`62971fd6 USER32!DefDlgProcWorker+0xd2 0f 00000000`00f9c9e0 00007ff8`6296e858 USER32!DefDlgProcW+0x36 10 00000000`00f9ca20 00007ff8`6296de1b USER32!UserCallWinProcCheckWow+0x2f8 11 00000000`00f9cbb0 00007ff8`6296d68a USER32!SendMessageWorker+0x70b 12 00000000`00f9cc50 00007ff8`4bb12467 USER32!SendMessageW+0xda 13 00000000`00f9cca0 00007ff8`4bb220f0 COMCTL32!Button_ReleaseCapture+0xbb 14 00000000`00f9ccd0 00007ff8`6296e858 COMCTL32!Button_WndProc+0x800 15 00000000`00f9ce00 00007ff8`6296e299 USER32!UserCallWinProcCheckWow+0x2f8 16 00000000`00f9cf90 00007ff6`0aa21a76 USER32!DispatchMessageWorker+0x249 17 00000000`00f9d010 00007ff6`0aa3a00f calc!WinMain+0x1db4 18 00000000`00f9f700 00007ff8`62207034 calc!__mainCRTStartup+0x18e 19 00000000`00f9f7c0 00007ff8`640c2651 KERNEL32!BaseThreadInitThunk+0x14 1a 00000000`00f9f7f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21 从notepad、calc示例看,"Coverage Diff"在某些场景可以较快地定位目标代码,不 过之前我没碰上什么非此不可的场景。 ☆ DynamoRIO 参[2] https://github.com/DynamoRIO/dynamorio/compare/cronbuild-8.0.18895...cronbuild-8.0.18901 在这个URL中搜"drcov" cronbuild-8.0.18895生成的是"DRCOV VERSION: 2" cronbuild-8.0.18901生成的是"DRCOV VERSION: 3" 我用二分法定位这两个处于分叉点的小版本号,正经应该怎么找? 9.0.1生成的.log文件,Lighthouse不认,因为是"DRCOV VERSION: 3"。 X:\Green\DynamoRIO-Windows-8.0.18895\bin64\drrun.exe -verbose -64 -t drcov -- notepad X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- notepad notepad tasklist | findstr notepad X:\Green\DynamoRIO-Windows-9.93.19545\bin64\drrun.exe -verbose -64 -attach -t drcov 录制时,必须终止目标进程以生成log。假设Ctrl-C退出drrun.exe,只会生成0字节 log,目标进程不会终止,还能正常用;若再次Attach同一pid,目标进程异常退出。 C:\Users\scz\dynamorio\ %USERPROFILE%\dynamorio\ drrun.exe会在上述目录产生垃圾,删之。 1) .log文件格式 以DR 9.0.1生成的.log为例 -------------------------------------------------------------------------- DRCOV VERSION: 3 DRCOV FLAVOR: drcov Module Table: version 5, count 46 Columns: id, containing_id, start, end, entry, offset, preferred_base, checksum, timestamp, path 0, 0, 0x0000000071000000, 0x00000000711c0000, 0x00000000710a2e70, 0000000000000000, 0x0000000071000000, 0x0019203b, 0x620aa6ec, X:\Green\DynamoRIO-Windows-9.0.1\lib64\release\dynamorio.dll 1, 1, 0x00007ff6309c0000, 0x00007ff6309c6000, 0x00007ff6309c0000, 0000000000000000, 0x0000000072000000, 0x0000ab8b, 0x620aa777, X:\Green\DynamoRIO-Windows-9.0.1\tools\lib64\release\drcov.dll ... 6, 6, 0x00007ff690980000, 0x00007ff6909b9000, 0x00007ff6909a5410, 0000000000000000, 0x00007ff690980000, 0x0003f1d3, 0x3ba63618, C:\Windows\system32\notepad.exe ... 45, 45, 0x00007ff857650000, 0x00007ff857749000, 0x00007ff85768e070, 0000000000000000, 0x00007ff857650000, 0x001035b4, 0x876e4e6b, C:\Windows\System32\TextInputFramework.dll BB Table: 99645 bbs -------------------------------------------------------------------------- 8.0.18895与9.0.1生成的.log相比有一重要区别,前者生成"DRCOV VERSION: 2"。 .log文件格式随版本变动较多的是"Module Table",参看Lighthouse的drcov.py,其 中有"class DrcovModule"。 据说Lighthouse实际用到的只有其中4个字段 id start end path 各版本的"BB Table"基本无变化,缺省是二进制字节流,不可读。 参看 https://github.com/DynamoRIO/dynamorio/blob/master/ext/drcovlib/drcovlib.h "BB Table"是bb_entry_t结构数组,bb_entry_t定义如下 -------------------------------------------------------------------------- /* * Data structure for the coverage info itself */ typedef struct _bb_entry_t { /* * The offset of the bb start from the containing segment base. We do * not support a single module segment larger than 4GB. */ uint start; /* * The size of the basic block */ ushort size; /* * The id of the module where the basic block is found. This * corresponds to the id assigned to the module when generating the * module table */ ushort mod_id; } bb_entry_t; -------------------------------------------------------------------------- 2) 文本格式的.log文件 参看 X:\Green\DynamoRIO-Windows-9.0.1\docs\html\page_drcov.html X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -dump_text -- notepad 这样生成的.log文件是纯文本的,"BB Table"可读,Lighthouse也支持。形如 -------------------------------------------------------------------------- BB Table: 122184 bbs module id, start, size: module[ 21]: 0x0000000000052630, 19 module[ 21]: 0x0000000000052643, 14 module[ 21]: 0x000000000008c620, 29 module[ 21]: 0x000000000008c63d, 6 -------------------------------------------------------------------------- 但这种.log文件比缺省格式大很多,如非必要,不推荐。 3) DRCOV VERSION 2 vs 3 https://github.com/DynamoRIO/dynamorio/pull/5094 https://github.com/DynamoRIO/dynamorio/pull/5136 官方说版本2与3的区别如下 Version 3 changes drcov's output to uses the module segment offset, rather than the whole module base offset as in version 2. This better supports modules with code beyond the first segment and with gaps between segments. X:\Green\DynamoRIO-Windows-8.0.18895\bin64\drrun.exe -verbose -64 -t drcov -dump_text -- test 1 2 X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -dump_text -- test 1 2 用"-dump_text"生成两份纯文本的.log,用BC比较,未发现"Module Table"的offset 字段有显著变化。作者举例用的是ELF,是不是PE完全不受影响? 4) 无法捕捉Win10 Store App的数据 "drrun.exe -t drcov"可以捕捉传统PE的数据,但在我的Win10环境中,无法捕捉 Store App的数据。在Win10上测试过 X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -t drcov -- X:\Green\DynamoRIO-Windows-9.0.1\bin64\drrun.exe -verbose -64 -attach -t drcov 目标进程对应传统PE时,比如notepad,可以捕捉数据,但始终无法捕捉Calculator 的数据。 云海提到"plmdebug /enableDebug",Attach调试UWP计算器无需plmdebug介入。后来 云海证实,本小节问题与plmdebug无关。 受限于UWP的Mitigation Policies,DynamoRIO缺省无法录制UWP计算器。理论上可以 改DynamoRIO,使用反射式DLL加载,而非LoadLibrary,使之支持UWP,但我懒得搞。 该需求后来用ttd-bindings变相满足了,可以录制UWP并截取指定区间,从而让 Coverage Diff更有意义。但是,就Win10 Calculator而言,其代码逻辑决定了不适 用Coverage Diff技术。 对Win10 Calculator测试Coverage Diff,没有符号,它的算术运算逻辑不那么独立, 乘法运算位于某个公共函数中,该公共函数被频繁调用,很难制造a.log不覆盖该公 共函数、b.log覆盖该公共函数的效果,处理Win7 calc的套路在此不适用。 关于ttd-bindings,参看 《TTD调试进阶之ttd-bindings》 https://scz.617.cn/windows/202207271620.txt 4.1) 讨论 4.1.3) ForceRelocateImages(终级原因) 参看 《DynamoRIO 9无法捕捉UWP数据的终级原因》 https://scz.617.cn/windows/202211140944.txt 要点是,Calculator开了这个 [+0x000 ( 4: 4)] ForceRelocateImages : 0x1 [Type: unsigned long] 在内核调试器中直接改_EPROCESS的MitigationFlags,去掉Calculator的 ForceRelocateImages,就可以加载dynamorio.dll;后者设置了 IMAGE_FILE_RELOCS_STRIPPED,过不了ForceRelocateImages的检查。 ☆ 参考资源 [1] Lighthouse - A Coverage Explorer for Reverse Engineers https://github.com/gaasedelen/lighthouse [2] DynamoRIO (Dynamic Instrumentation Tool Platform) https://dynamorio.org/ https://github.com/DynamoRIO/dynamorio [3] Dragon Dance https://github.com/domenukk/dragondance (a plugin for Ghidra)