Lighthouse/DynamoRIO/Coverage Diff入门

一、原始需求

Win10的calc与Win7的calc不一样,前者是个Store App,对应Calculator.exe。

启动Win10的calc,切换到”程序员模式”,切换到16进制模式,依次输入

51201314 * 41414141 =

得到乘法运算结果

0x14add2aaaa10ec14

原始需求是,寻找前述算术运算相关代码。

参看

《MSDN系列(46)–WinDbg Preview TTD入门》
https://blog.nsfocus.net/sdn-46windbg-preview-ttd/

上篇我用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

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:
00007ff69098aa86 e869b90100 call notepad!memset (00007ff6909a63f4)

与之前用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“,退出。 第二次测试,输入”5120131441414141=”,退出。

用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:
00007ff60aa26f50 e857e5ffff call calc!mulrat (00007ff60aa254ac)

另开一个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 0000000000f9bc00 00007ff60aa251d0 calc!_mulnumx+0xa2
01 0000000000f9bc50 00007ff60aa254da calc!mulnumx+0x92
02 0000000000f9bc80 00007ff60aa26f55 calc!mulrat+0x2a
03 0000000000f9bcb0 00007ff60aa26967 calc!CCalcEngine::DoOperation+0x5fe
04 0000000000f9bd30 00007ff60aa249c6 calc!CCalcEngine::ProcessCommandWorker+0x119c
05 0000000000f9bea0 00007ff60aa24938 calc!CCalcEngine::ProcessCommand+0x2a
06 0000000000f9bed0 00007ff60aa2460a calc!CUIController::ProcessKeypadInput+0xaa
07 0000000000f9bf10 00007ff60aa24744 calc!CContainer::ProcessKeyPadInputs+0x7a1
08 0000000000f9c070 00007ff86296e858 calc!WndProc+0xa12
09 0000000000f9c550 00007ff86296de1b USER32!UserCallWinProcCheckWow+0x2f8
0a 0000000000f9c6e0 00007ff86296d68a USER32!SendMessageWorker+0x70b
0b 0000000000f9c780 00007ff60aa30a60 USER32!SendMessageW+0xda
0c 0000000000f9c7d0 00007ff862972920 calc!CProgrammerMode::ProgDlgProc+0x157
0d 0000000000f9c840 00007ff8629720c2 USER32!UserCallDlgProcCheckWow+0x144
0e 0000000000f9c920 00007ff862971fd6 USER32!DefDlgProcWorker+0xd2
0f 0000000000f9c9e0 00007ff86296e858 USER32!DefDlgProcW+0x36
10 0000000000f9ca20 00007ff86296de1b USER32!UserCallWinProcCheckWow+0x2f8
11 0000000000f9cbb0 00007ff86296d68a USER32!SendMessageWorker+0x70b
12 0000000000f9cc50 00007ff84bb12467 USER32!SendMessageW+0xda
13 0000000000f9cca0 00007ff84bb220f0 COMCTL32!Button_ReleaseCapture+0xbb
14 0000000000f9ccd0 00007ff86296e858 COMCTL32!Button_WndProc+0x800
15 0000000000f9ce00 00007ff86296e299 USER32!UserCallWinProcCheckWow+0x2f8
16 0000000000f9cf90 00007ff60aa21a76 USER32!DispatchMessageWorker+0x249
17 0000000000f9d010 00007ff60aa3a00f calc!WinMain+0x1db4
18 0000000000f9f700 00007ff862207034 calc!__mainCRTStartup+0x18e
19 0000000000f9f7c0 00007ff8640c2651 KERNEL32!BaseThreadInitThunk+0x14
1a 0000000000f9f7f0 0000000000000000 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

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”基本无变化,缺省是二进制字节流,不可读。

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也支持。但这种.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”。我的环境中,cdb调试Calculator无需plmdebug
介入。试用过该命令,没能让DynamoRIO捕捉Win10 Store App的数据。

参考资源

[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)

版权声明

本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。

Spread the word. Share this post!

Meet The Author

C/ASM程序员

Leave Comment