軟件漏洞分析的八大技巧匯總
在日常分析軟件漏洞時,經常需要耗費比較長的分析時間,少則幾小時,多則數天,甚至更久。因此,經常總結一些分析技巧是非常有必要的,針對不同的漏洞類型采取不同的分析思路和技巧,可以有效地提高分析速度。對于一些被曝出來的熱門0day,網上一般都會有分析文章,但一般都是“結論性分析”,也就是直接帖漏洞代碼,指出哪里出錯,而非“思路性分析”。如果你經常分析漏洞的話,會發現占用你分析時間的往往不是分析漏洞代碼,而是定位漏洞代碼。所以說,調試分析漏洞有時就是看下斷點下得準不,再加上一些胡猜亂想來推測,最后才是分析漏洞代碼了,如果熟悉匯編指令,這個就不是問題了。
下面是筆者就以往分析過的若干實例漏洞,總結出的一些小技巧。不過,技巧甚多,篇幅有限,此處僅列舉一些比較個人常用的方法,也歡迎各位分享自己的一些分析技巧,大家共同學習探討。
技巧一:快速定位JS代碼調用的IE類成員函數
CVE-2011-0027 Microsoft Data Access組件整數溢出漏洞是Pwn2Own 2010黑客大賽中被用來攻破IE8瀏覽器的漏洞,其中關鍵的漏洞觸發代碼如下:
localxmlid1 = document.getElementById('xmlid1').recordset; // 獲取xml元素xmlid1的recordset,即數據庫表的記錄集
localxmlid1.CacheSize = 0x40000358; // 設置能夠被保存的記錄條數,此值最終造成整數溢出
現在我們就介紹一種快速定位上述兩行代碼將對應調用的IE類成員函數,首先用IDA加載漏洞文件msado15.dll,并允許加載微軟符號表,然后選中“Function name”一欄,按“Alt + T”快捷鍵彈出搜索框,輸入搜索關鍵字“cachesize”:
通過按“Ctrl+T”可繼續搜索下一個,最后找到兩個相關函數:
CRecordset::put_CacheSize(long *)
CRocordset::get_CacheSize(long)
我們對CRecordset::put_CacheSize下斷點驗證下前面的猜測,用Windbg附加IE進程運行后打開poc.html,確實斷在put_CacheSize,通過查看參數可以看到poc.html中設置的CacheSize值0×40000358,說明CRecordset::put_CacheSize確實是設置CacheSize值的函數:
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
我們對CRecordset::put_CacheSize下斷點驗證下前面的猜測,用Windbg附加IE進程運行后打開poc.html,確實斷在put_CacheSize,通過查看參數可以看到poc.html中設置的CacheSize值0×40000358,說明CRecordset::put_CacheSize確實是設置CacheSize值的函數。#p#
技巧二:通過頁堆快速定位堆漏洞代碼
頁堆是windows 2000 引入的調試支持功能,簡稱DPH(Debug Page Heap),啟用該機制后,堆管理器會在堆塊后增加專門用于檢測溢出的柵欄頁,當數據溢出觸及柵欄頁便會立刻觸發異常,此時往往就是觸發漏洞的最及時的位置,它不僅適用于堆溢出,對于其它類型的堆漏洞也是適用的。
以CVE-2013-0077 微軟DirectShow堆溢出漏洞為例,通過以下命令開啟頁堆(gflags):
gflags.exe –i player.exe +hpa
開啟頁堆hpa后,重新附加運行后,在復制數據到堆邊界時斷下:
(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]
上面就是斷在復制數據導致溢出的指令,通過分析其所在函數往往很容易定位漏洞代碼。如果不開啟頁堆,直接以默認形式調試的話,你會發現是斷在以下指令:
(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=????????
這已經是堆溢出后導致的內存讀取異常了,不再是觸發漏洞時最原始的場景了。因此開啟頁堆后,會更方便你去定位漏洞代碼。#p#
技巧三:基于堆分配記錄定位整數溢出漏洞代碼
以CVE-2011-0027 Microsoft Data Access組件整數溢出漏洞為例,這個漏洞就是在Pwn2Own 2010黑客大賽中,荷蘭黑客Peter Vreugdenhil利用它來攻破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
根據上面異常的信息,可以知道程序是在向地址為0x88c7298,大小0xd64的堆塊寫入數據時造成堆溢出了。再根據!heap命令中返回的棧回溯信息可以知道,被溢出的堆塊是在CRecordset::MoveFirst函數中調用MpHeapAlloc函數分配的,調用該分配函數的上層函數是CRecordGroup::AllocateHRowRange,對此函數下斷,跟進后:
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,經eax*4+4后變成0x100000D64 > 0xFFFFFFFF造成整數溢出,結果溢出后等于0xD64。如果我們把CacheSize設置成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)}
該漏洞主要是由于對CacheSize整數值未作有效判斷,導致經CacheSize*4+4造成整數溢出,當以CacheSize*4+4結果作為分配堆塊的大小時,由于分配過小堆塊就會造成堆溢出。#p#
技巧四:基于條件記錄斷點的漏洞分析技巧
以CVE-2012-0774 Adobe Reader TrueType字體整數溢出漏洞為例,該漏洞主要是在解析TTF字體中的虛擬指令導致的溢出,為了確定導致溢出的是哪條虛擬指令,就可以通過設置條件記錄斷點來實現。
打開poc觸發崩潰后,通過棧回溯定位漏洞函數,此處標記為VulFunction,它就是用于索引虛擬指令處理函數的。
通過快捷鍵Shift + F4對VulFunction下條件記錄斷點,記錄每次調用VulFunction時對應的虛擬指令索引號
設置好后重新加載AcroRd32.exe運行(不要關閉調試器否則前面設置的斷點可能失效),打開poc.pdf運行后查看日志窗口:
導致漏洞的虛擬指令索引號為0×26,通過蘋果官方提供的指令集https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html,可以查到字節碼0×26對應的虛擬指令為MINDEX,正是該條指令導致的溢出。#p#
技巧五:基于JS日志的漏洞分析技巧
有時在調試IE漏洞時,我們需要樣本中的JS代碼的執行先后情況,通過添加一些數學函數,比如math.atan2、math.asin等,還可以使用其它函數,然后通過對jscript!Js::Math::Atan(或者其它函數)下斷點來輸出log,以方便分析者觀察執行情況。比如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);
通過設置以下斷點:
bu jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g"
運行后在windbg上的輸出窗口就會看到:
[*] Creating object button...
[*] Assigning data to title...
[*] Let's AppendChild
#p#
技巧六:通過虛擬機快照來固定堆地址
最早聽到這個方法,是instruder在QQ群里面提到的。由于在分析漏洞時,尤其是堆漏洞時,每個重新加載運行時,分配的堆地址都是固定,無論是分析還是寫文檔,都不太利用于我們分析和描述。因此我們可以先將程序調試已經完成堆分配的某個地址,然后將其保存為虛擬機快照,等我們需要再重新開始調試時,可通過恢復先前保存的快照來重新調試,那么此時的堆地址跟之前分析的地址都是固定的。
技巧七:監控堆分配釋放動作來分析UAF、double free漏洞
以之前分配libpng某個double free漏洞的分析為例,主要是通過對釋放函數free進行監控并輸出記錄來分析的。程序崩潰時的棧回溯如下:
重新用windbg加載,通過監控堆塊的釋放過程,可以發現它在對其中某堆塊進行了雙重釋放,先下斷:
bu 3440D279 ".if(1){.echo EnterVulnFunc;gc}"
bu 6e264b6c ".if(1){.echo Free heap block; dd esp l4;gc}"
輸出結果:
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=??
可以看到它對同一個堆塊進行多次釋放。在IE漏洞分析中最常見的還是UAF,如果想快速定造成UAF的IE對象,就需要對對象的分配的釋放進行釋放,這個可通過調試器腳本來實現,或者像之前h4ckmp同學基于Windbg接口寫的EXE工具,可以快速分析出導致UAF漏洞的對象,如下圖所示。
技巧八:基于污點追蹤的分析方法
污點追蹤理論挺好的,但成品往往不容樂觀。污點追蹤猶如“七傷拳”一般,“先傷己,再傷人”,開發污點追蹤工具,不僅費時費力,而且開發完成后,運行比較大的工具往往需要運行很長時間,比如IE、Adobe等軟件,有時甚至需要整整一天的時間,這種一般是在調試分析不方便的時候才使用的,主要針對文件格式漏洞。另外,你也可利用pin等動態插樁框架開發出動態分析工具,針對特定函數掛鉤,比如堆分配與釋放函數,也可以用它來實現快速分析。