軟件漏洞分析的八大技巧匯總
在日常分析軟件漏洞時(shí),經(jīng)常需要耗費(fèi)比較長(zhǎng)的分析時(shí)間,少則幾小時(shí),多則數(shù)天,甚至更久。因此,經(jīng)??偨Y(jié)一些分析技巧是非常有必要的,針對(duì)不同的漏洞類型采取不同的分析思路和技巧,可以有效地提高分析速度。對(duì)于一些被曝出來(lái)的熱門0day,網(wǎng)上一般都會(huì)有分析文章,但一般都是“結(jié)論性分析”,也就是直接帖漏洞代碼,指出哪里出錯(cuò),而非“思路性分析”。如果你經(jīng)常分析漏洞的話,會(huì)發(fā)現(xiàn)占用你分析時(shí)間的往往不是分析漏洞代碼,而是定位漏洞代碼。所以說(shuō),調(diào)試分析漏洞有時(shí)就是看下斷點(diǎn)下得準(zhǔn)不,再加上一些胡猜亂想來(lái)推測(cè),最后才是分析漏洞代碼了,如果熟悉匯編指令,這個(gè)就不是問題了。
下面是筆者就以往分析過的若干實(shí)例漏洞,總結(jié)出的一些小技巧。不過,技巧甚多,篇幅有限,此處僅列舉一些比較個(gè)人常用的方法,也歡迎各位分享自己的一些分析技巧,大家共同學(xué)習(xí)探討。
技巧一:快速定位JS代碼調(diào)用的IE類成員函數(shù)
CVE-2011-0027 Microsoft Data Access組件整數(shù)溢出漏洞是Pwn2Own 2010黑客大賽中被用來(lái)攻破IE8瀏覽器的漏洞,其中關(guān)鍵的漏洞觸發(fā)代碼如下:
localxmlid1 = document.getElementById('xmlid1').recordset; // 獲取xml元素xmlid1的recordset,即數(shù)據(jù)庫(kù)表的記錄集
localxmlid1.CacheSize = 0x40000358; // 設(shè)置能夠被保存的記錄條數(shù),此值最終造成整數(shù)溢出
現(xiàn)在我們就介紹一種快速定位上述兩行代碼將對(duì)應(yīng)調(diào)用的IE類成員函數(shù),首先用IDA加載漏洞文件msado15.dll,并允許加載微軟符號(hào)表,然后選中“Function name”一欄,按“Alt + T”快捷鍵彈出搜索框,輸入搜索關(guān)鍵字“cachesize”:
通過按“Ctrl+T”可繼續(xù)搜索下一個(gè),最后找到兩個(gè)相關(guān)函數(shù):
CRecordset::put_CacheSize(long *)
CRocordset::get_CacheSize(long)
我們對(duì)CRecordset::put_CacheSize下斷點(diǎn)驗(yàn)證下前面的猜測(cè),用Windbg附加IE進(jìn)程運(yùn)行后打開poc.html,確實(shí)斷在put_CacheSize,通過查看參數(shù)可以看到poc.html中設(shè)置的CacheSize值0×40000358,說(shuō)明CRecordset::put_CacheSize確實(shí)是設(shè)置CacheSize值的函數(shù):
0:005> g
Breakpoint 1 hit
eax=40000358 ebx=04bdcfd8 ecx=6e61d340 edx=00000000 esi=01fbf144 edi=00000000
eip=6e6ac957 esp=01fbeb58 ebp=01fbf040 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
msado15!CRecordset::put_CacheSize:
6e6ac957 8bff mov edi,edi
0:005> dd esp
01fbeb58 6e62f3ec 04bdcfd8 40000358 00000000
01fbeb68 01fbf074 04bdcfd8 11000011 00000000
01fbeb78 03570ae8 004ad070 00538a30 00000088
01fbeb88 00470000 00000002 03570760 01fbec84
01fbeb98 76fc3193 00470138 76fc316f 764736b8
01fbeba8 00000000 00470000 03570768 00518a78
01fbebb8 004767b8 004768e4 00559258 00476db8
01fbebc8 76f8d74d 0051d968 00472a98 01fbedc0
我們對(duì)CRecordset::put_CacheSize下斷點(diǎn)驗(yàn)證下前面的猜測(cè),用Windbg附加IE進(jìn)程運(yùn)行后打開poc.html,確實(shí)斷在put_CacheSize,通過查看參數(shù)可以看到poc.html中設(shè)置的CacheSize值0×40000358,說(shuō)明CRecordset::put_CacheSize確實(shí)是設(shè)置CacheSize值的函數(shù)。#p#
技巧二:通過頁(yè)堆快速定位堆漏洞代碼
頁(yè)堆是windows 2000 引入的調(diào)試支持功能,簡(jiǎn)稱DPH(Debug Page Heap),啟用該機(jī)制后,堆管理器會(huì)在堆塊后增加專門用于檢測(cè)溢出的柵欄頁(yè),當(dāng)數(shù)據(jù)溢出觸及柵欄頁(yè)便會(huì)立刻觸發(fā)異常,此時(shí)往往就是觸發(fā)漏洞的最及時(shí)的位置,它不僅適用于堆溢出,對(duì)于其它類型的堆漏洞也是適用的。
以CVE-2013-0077 微軟DirectShow堆溢出漏洞為例,通過以下命令開啟頁(yè)堆(gflags):
gflags.exe –i player.exe +hpa
開啟頁(yè)堆hpa后,重新附加運(yùn)行后,在復(fù)制數(shù)據(jù)到堆邊界時(shí)斷下:
(4b8.358): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=000000c3 ebx=003fac98 ecx=00000003 edx=000000f7 esi=001bbdd4 edi=003fb000
eip=7d0706d0 esp=02a5f650 ebp=02a5f658 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010202
quartz!ParseSequenceHeader+0x114:
7d0706d0 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
上面就是斷在復(fù)制數(shù)據(jù)導(dǎo)致溢出的指令,通過分析其所在函數(shù)往往很容易定位漏洞代碼。如果不開啟頁(yè)堆,直接以默認(rèn)形式調(diào)試的話,你會(huì)發(fā)現(xiàn)是斷在以下指令:
(4c8.6bc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=41414141 ebx=003f0000 ecx=41414141 edx=03128e40 esi=03128e38 edi=00000012
eip=7c930efe esp=0465f998 ebp=0465fbb8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
ntdll!RtlAllocateHeap+0x653:
7c930efe 8b39 mov edi,dword ptr [ecx] ds:0023:41414141=????????
這已經(jīng)是堆溢出后導(dǎo)致的內(nèi)存讀取異常了,不再是觸發(fā)漏洞時(shí)最原始的場(chǎng)景了。因此開啟頁(yè)堆后,會(huì)更方便你去定位漏洞代碼。#p#
技巧三:基于堆分配記錄定位整數(shù)溢出漏洞代碼
以CVE-2011-0027 Microsoft Data Access組件整數(shù)溢出漏洞為例,這個(gè)漏洞就是在Pwn2Own 2010黑客大賽中,荷蘭黑客Peter Vreugdenhil利用它來(lái)攻破Win7上的IE8瀏覽器的。
下面是打開poc.html后的異常情況:
(7b8.278): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000036b ebx=0000035b ecx=00000000 edx=00000001 esi=088c8000 edi=00000000
eip=6887746f esp=044dee84 ebp=044dee88 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
mshtml!CImpIRowset::HRowNumber2HROWQuiet+0x23:
6887746f 8906 mov dword ptr [esi],eax ds:0023:088c8000=????????
0:005> !heap -p -a 088c8000
address 088c8000 found in
_DPH_HEAP_ROOT @ 8821000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
88227ec: 88c7298 d64- 88c7000 2000
72eb8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77034ea6 ntdll!RtlDebugAllocateHeap+0x00000030
76ff7d96 ntdll!RtlpAllocateHeap+0x000000c4
76fc34ca ntdll!RtlAllocateHeap+0x0000023a
730d975d MSDART!MpHeapAlloc+0x00000029
6e5406e7 msado15!CRecordGroup::AllocateHRowRange+0x00000085
6e540650 msado15!CRecordset::PrepareForFetch+0x000000e2
6e5d44ae msado15!CRecordset::MoveAbsolute+0x000003e3
6e5680a5 msado15!CRecordset::_MoveFirst+0x0000007d
6e5d7957 msado15!CRecordset::MoveFirst+0x00000221
6e54fde6 msado15!CRecordset::Invoke+0x00001560
71dcdb38 jscript!IDispatchInvoke2+0x000000f0
71dcda8c jscript!IDispatchInvoke+0x0000006a
71dcd9ff jscript!InvokeDispatch+0x000000a9
71dcdb8a jscript!VAR::InvokeByName+0x00000093
71dcd8c8 jscript!VAR::InvokeDispName+0x0000007d
71dcd96f jscript!VAR::InvokeByDispID+0x000000ce
71dce3e7 jscript!CScriptRuntime::Run+0x00002b80
71dc5c9d jscript!ScrFncObj::CallWithFrameOnStack+0x000000ce
71dc5bfb jscript!ScrFncObj::Call+0x0000008d
71dc5e11 jscript!CSession::Execute+0x0000015f
71dbf3ee jscript!NameTbl::InvokeDef+0x000001b5
71dbea2e jscript!NameTbl::InvokeEx+0x0000012c
71db96de jscript!NameTbl::Invoke+0x00000070
685aaa7b mshtml!CWindow::ExecuteTimeoutScript+0x00000087
685aab66 mshtml!CWindow::FireTimeOut+0x000000b6
685d6af7 mshtml!CStackPtrAry<unsigned long,12>::GetStackSize+0x000000b6
685d1e57 mshtml!GlobalWndProc+0x00000183
770e86ef USER32!InternalCallWinProc+0x00000023
770e8876 USER32!UserCallWinProcCheckWow+0x0000014b
770e89b5 USER32!DispatchMessageWorker+0x0000035e
770e8e9c USER32!DispatchMessageW+0x0000000f
根據(jù)上面異常的信息,可以知道程序是在向地址為0x88c7298,大小0xd64的堆塊寫入數(shù)據(jù)時(shí)造成堆溢出了。再根據(jù)!heap命令中返回的?;厮菪畔⒖梢灾?,被溢出的堆塊是在CRecordset::MoveFirst函數(shù)中調(diào)用MpHeapAlloc函數(shù)分配的,調(diào)用該分配函數(shù)的上層函數(shù)是CRecordGroup::AllocateHRowRange,對(duì)此函數(shù)下斷,跟進(jìn)后:
Breakpoint 0 hit
eax=00000001 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=40000358eip=69da06be esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
msado15!CRecordGroup::AllocateHRowRange+0x5e:
69da06be 85ff test edi,edi0:005> p
eax=00000001 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=40000358
eip=69da06c0 esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x60:
69da06c0 0f8e34380200 jle msado15!CRecordGroup::AllocateHRowRange+0x62 (69dc3efa) [br=0]
0:005> p
eax=00000001 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=40000358
eip=69da06c6 esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x64:
69da06c6 8bc7 mov eax,edi // eax = edi = 0x40000358,即CacheSize0:005> p
eax=40000358 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=40000358
eip=69da06c8 esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x66:
69da06c8 8b3dfc10d969 mov edi,dword ptr [msado15!_imp__MpHeapAlloc (69d910fc)] ds:0023:69d910fc={MSDART!MpHeapAlloc (72de9730)}
0:005> p
eax=40000358 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06ce esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x6c:
69da06ce 89460c mov dword ptr [esi+0Ch],eax ds:0023:09759d7c=00000000
0:005> p
eax=40000358 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06d1 esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x6f:
69da06d1 8b0d10f0e569 mov ecx,dword ptr [msado15!g_hHeapHandle (69e5f010)] ds:0023:69e5f010=096f0000
0:005> p
eax=40000358 ebx=40000358 ecx=096f0000 edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06d7 esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x75:
69da06d7 8d048504000000 lea eax,[eax*4+4] // 分配的堆塊大小= 0x40000358*4+4 = 0xd64,這里eax的最大值是0xFFFFFFFF,經(jīng)eax*4+4后變成0x100000D64 > 0xFFFFFFFF造成整數(shù)溢出,結(jié)果溢出后等于0xD64。如果我們把CacheSize設(shè)置成0x3FFFFFFF,那么后面分配的堆塊就是0了。0:005> p
eax=00000d64 ebx=40000358 ecx=096f0000 edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06de esp=0446eeec ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x7c:
69da06de 50 push eax // 堆塊大小=0xd640:005> p
eax=00000d64 ebx=40000358 ecx=096f0000 edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06df esp=0446eee8 ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x7d:
69da06df 680000a000 push 0A00000h
0:005> p
eax=00000d64 ebx=40000358 ecx=096f0000 edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06e4 esp=0446eee4 ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x82:
69da06e4 51 push ecx
0:005> p
eax=00000d64 ebx=40000358 ecx=096f0000 edx=096f2d34 esi=09759d70 edi=72de9730
eip=69da06e5 esp=0446eee0 ebp=0446eef8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x83:
69da06e5 ffd7 call edi {MSDART!MpHeapAlloc (72de9730)}
該漏洞主要是由于對(duì)CacheSize整數(shù)值未作有效判斷,導(dǎo)致經(jīng)CacheSize*4+4造成整數(shù)溢出,當(dāng)以CacheSize*4+4結(jié)果作為分配堆塊的大小時(shí),由于分配過小堆塊就會(huì)造成堆溢出。#p#
技巧四:基于條件記錄斷點(diǎn)的漏洞分析技巧
以CVE-2012-0774 Adobe Reader TrueType字體整數(shù)溢出漏洞為例,該漏洞主要是在解析TTF字體中的虛擬指令導(dǎo)致的溢出,為了確定導(dǎo)致溢出的是哪條虛擬指令,就可以通過設(shè)置條件記錄斷點(diǎn)來(lái)實(shí)現(xiàn)。
打開poc觸發(fā)崩潰后,通過?;厮荻ㄎ宦┒春瘮?shù),此處標(biāo)記為VulFunction,它就是用于索引虛擬指令處理函數(shù)的。
通過快捷鍵Shift + F4對(duì)VulFunction下條件記錄斷點(diǎn),記錄每次調(diào)用VulFunction時(shí)對(duì)應(yīng)的虛擬指令索引號(hào)
設(shè)置好后重新加載AcroRd32.exe運(yùn)行(不要關(guān)閉調(diào)試器否則前面設(shè)置的斷點(diǎn)可能失效),打開poc.pdf運(yùn)行后查看日志窗口:
導(dǎo)致漏洞的虛擬指令索引號(hào)為0×26,通過蘋果官方提供的指令集https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html,可以查到字節(jié)碼0×26對(duì)應(yīng)的虛擬指令為MINDEX,正是該條指令導(dǎo)致的溢出。#p#
技巧五:基于JS日志的漏洞分析技巧
有時(shí)在調(diào)試IE漏洞時(shí),我們需要樣本中的JS代碼的執(zhí)行先后情況,通過添加一些數(shù)學(xué)函數(shù),比如math.atan2、math.asin等,還可以使用其它函數(shù),然后通過對(duì)jscript!Js::Math::Atan(或者其它函數(shù))下斷點(diǎn)來(lái)輸出log,以方便分析者觀察執(zhí)行情況。比如poc中有如下js,其中的math.atan2是我們添加的:
Math.atan2(0xbabe, "[*] Creating object button...");
var obj = document.createElement("button");Math.atan2(0xbabe, "[*] Assigning data to title...");
obj.title = data.substring(0,0x40000-0x58);Math.atan2(0xbabe, "[*] Let's AppendChild");
div_container.appendChild(obj);
通過設(shè)置以下斷點(diǎn):
bu jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g"
運(yùn)行后在windbg上的輸出窗口就會(huì)看到:
[*] Creating object button...
[*] Assigning data to title...
[*] Let's AppendChild
#p#
技巧六:通過虛擬機(jī)快照來(lái)固定堆地址
最早聽到這個(gè)方法,是instruder在QQ群里面提到的。由于在分析漏洞時(shí),尤其是堆漏洞時(shí),每個(gè)重新加載運(yùn)行時(shí),分配的堆地址都是固定,無(wú)論是分析還是寫文檔,都不太利用于我們分析和描述。因此我們可以先將程序調(diào)試已經(jīng)完成堆分配的某個(gè)地址,然后將其保存為虛擬機(jī)快照,等我們需要再重新開始調(diào)試時(shí),可通過恢復(fù)先前保存的快照來(lái)重新調(diào)試,那么此時(shí)的堆地址跟之前分析的地址都是固定的。
技巧七:監(jiān)控堆分配釋放動(dòng)作來(lái)分析UAF、double free漏洞
以之前分配libpng某個(gè)double free漏洞的分析為例,主要是通過對(duì)釋放函數(shù)free進(jìn)行監(jiān)控并輸出記錄來(lái)分析的。程序崩潰時(shí)的棧回溯如下:
重新用windbg加載,通過監(jiān)控堆塊的釋放過程,可以發(fā)現(xiàn)它在對(duì)其中某堆塊進(jìn)行了雙重釋放,先下斷:
bu 3440D279 ".if(1){.echo EnterVulnFunc;gc}"
bu 6e264b6c ".if(1){.echo Free heap block; dd esp l4;gc}"
輸出結(jié)果:
EnterVulnFunc
Free heap block
0011bc5c 3441e2a2 138f0020 3b906313 10027b64
Free heap block
0011bc5c 3441dc6c 138f0020 3b906313 10027b64
(1508.e84): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=138f0018 ebx=138f0020 ecx=6e287a7e edx=10028a70 esi=008a0000 edi=00000000
eip=77691f88 esp=0011bbe8 ebp=0011bbf8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
ntdll!RtlFreeHeap+0x3a:
77691f88 80780705 cmp byte ptr [eax+7],5 ds:0023:138f001f=??
可以看到它對(duì)同一個(gè)堆塊進(jìn)行多次釋放。在IE漏洞分析中最常見的還是UAF,如果想快速定造成UAF的IE對(duì)象,就需要對(duì)對(duì)象的分配的釋放進(jìn)行釋放,這個(gè)可通過調(diào)試器腳本來(lái)實(shí)現(xiàn),或者像之前h4ckmp同學(xué)基于Windbg接口寫的EXE工具,可以快速分析出導(dǎo)致UAF漏洞的對(duì)象,如下圖所示。
技巧八:基于污點(diǎn)追蹤的分析方法
污點(diǎn)追蹤理論挺好的,但成品往往不容樂觀。污點(diǎn)追蹤猶如“七傷拳”一般,“先傷己,再傷人”,開發(fā)污點(diǎn)追蹤工具,不僅費(fèi)時(shí)費(fèi)力,而且開發(fā)完成后,運(yùn)行比較大的工具往往需要運(yùn)行很長(zhǎng)時(shí)間,比如IE、Adobe等軟件,有時(shí)甚至需要整整一天的時(shí)間,這種一般是在調(diào)試分析不方便的時(shí)候才使用的,主要針對(duì)文件格式漏洞。另外,你也可利用pin等動(dòng)態(tài)插樁框架開發(fā)出動(dòng)態(tài)分析工具,針對(duì)特定函數(shù)掛鉤,比如堆分配與釋放函數(shù),也可以用它來(lái)實(shí)現(xiàn)快速分析。