軟體漏洞分析技巧分享

wyzsk發表於2020-08-19
作者: 騰訊安全中心 · 2014/03/20 11:03

作者:riusksk

0x00 背景


在日常分析軟體漏洞時,經常需要耗費比較長的分析時間,少則幾小時,多則數天,甚至更久。因此,經常總結一些分析技巧是非常有必要的,針對不同的漏洞型別採取不同的分析思路和技巧,可以有效地提高分析速度。對於一些被曝出來的熱門0day,網上一般都會有分析文章,但一般都是“結論性分析”,也就是直接帖漏洞程式碼,指出哪裡出錯,而非“思路性分析”。如果你經常分析漏洞的話,會發現佔用你分析時間的往往不是分析漏洞程式碼,而是定位漏洞程式碼。所以說,除錯分析漏洞有時就是看下斷點下得準不,再加上一些胡猜亂想來推測,最後才是分析漏洞程式碼了,如果熟悉彙編指令,這個就不是問題了。

下面是筆者就以往分析過的若干例項漏洞,總結出的一些小技巧。不過,技巧甚多,篇幅有限,此處僅列舉一些比較個人常用的方法,也歡迎各位分享自己的一些分析技巧,大家共同學習探討。

0x01 技巧


技巧一:快速定位JS程式碼呼叫的IE類成員函式

CVE-2011-0027 Microsoft Data Access元件整數溢位漏洞是Pwn2Own 2010駭客大賽中被用來攻破IE8瀏覽器的漏洞,其中關鍵的漏洞觸發程式碼如下:

#!js
localxmlid1 = document.getElementById('xmlid1').recordset; // 獲取xml元素xmlid1的recordset,即資料庫表的記錄集
localxmlid1.CacheSize = 0x40000358; // 設定能夠被儲存的記錄條數,此值最終造成整數溢位

現在我們就介紹一種快速定位上述兩行程式碼將對應呼叫的IE類成員函式,首先用IDA載入漏洞檔案msado15.dll,並允許載入微軟符號表,然後選中“Function name”一欄,按“Alt + T”快捷鍵彈出搜尋框,輸入搜尋關鍵字“cachesize”:

NewImage

透過按“Ctrl+T”可繼續搜尋下一個,最後找到兩個相關函式:

NewImage

根據函式名猜測下面的函式可能就是用於設定和獲取cachesize屬性值的:

#!cpp
CRecordset::put*CacheSize(long *)
CRocordset::get*CacheSize(long)

我們對CRecordset::put_CacheSize下斷點驗證下前面的猜測,用Windbg附加IE程式執行後開啟poc.html,確實斷在put_CacheSize,透過檢視引數可以看到poc.html中設定的CacheSize值0x40000358,說明CRecordset::put_CacheSize確實是設定CacheSize值的函式:

#!bash
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

說明 localxmlid1.CacheSize = 0x40000358 這行設定CacheSize的JS程式碼對應呼叫的就是CRecordset::put_CacheSize 函式。採用同類方法也很容易確認 document.getElementById('xmlid1').recordset 呼叫的是CGenericElement::get_recordset函式,各位朋友可以自己動手試下。

技巧二:透過頁堆快速定位堆漏洞程式碼

頁堆是windows 2000 引入的除錯支援功能,簡稱DPH(Debug Page Heap),啟用該機制後,堆管理器會在堆塊後增加專門用於檢測溢位的柵欄頁,當資料溢位觸及柵欄頁便會立刻觸發異常,此時往往就是觸發漏洞的最及時的位置,它不僅適用於堆溢位,對於其它型別的堆漏洞也是適用的。

以CVE-2013-0077 微軟DirectShow堆溢位漏洞為例,透過以下命令開啟頁堆(gflags):

#!bash
gflags.exe –i player.exe +hpa 

開啟頁堆hpa後,重新附加執行後,在複製資料到堆邊界時斷下:

#!bash
(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]

上面就是斷在複製資料導致溢位的指令,透過分析其所在函式往往很容易定位漏洞程式碼。如果不開啟頁堆,直接以預設形式除錯的話,你會發現是斷在以下指令:

#!bash
(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=????????

這已經是堆溢位後導致的記憶體讀取異常了,不再是觸發漏洞時最原始的場景了。因此開啟頁堆後,會更方便你去定位漏洞程式碼。

技巧三:基於堆分配記錄定位整數溢位漏洞程式碼

以CVE-2011-0027 Microsoft Data Access元件整數溢位漏洞為例,這個漏洞就是在Pwn2Own 2010駭客大賽中,荷蘭駭客Peter Vreugdenhil利用它來攻破Win7上的IE8瀏覽器的。

下面是開啟poc.html後的異常情況:

#!bash
(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,對此函式下斷,跟進後:

#!bash
Breakpoint 0 hit
eax=00000001 ebx=40000358 ecx=76fc316f edx=096f2d34 esi=09759d70 edi=40000358
eip=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,edi
0: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,即CacheSize
0: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    // 堆塊大小=0xd64
0: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整數值未作有效判斷,導致經CacheSize4+4造成整數溢位,當以CacheSize4+4結果作為分配堆塊的大小時,由於分配過小堆塊就會造成堆溢位。

技巧四:基於條件記錄斷點的漏洞分析技巧

以CVE-2012-0774 Adobe Reader TrueType字型整數溢位漏洞為例,該漏洞主要是在解析TTF字型中的虛擬指令導致的溢位,為了確定導致溢位的是哪條虛擬指令,就可以透過設定條件記錄斷點來實現。 開啟poc觸發崩潰後,透過棧回溯定位漏洞函式,此處標記為VulFunction,它就是用於索引虛擬指令處理函式的。

NewImage

透過快捷鍵Shift + F4對VulFunction下條件記錄斷點,記錄每次呼叫VulFunction時對應的虛擬指令索引號

NewImage

設定好後重新載入AcroRd32.exe執行(不要關閉偵錯程式否則前面設定的斷點可能失效),開啟poc.pdf執行後檢視日誌視窗:

NewImage

導致漏洞的虛擬指令索引號為0x26,透過蘋果官方提供的指令集https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html,可以查到位元組碼0x26對應的虛擬指令為MINDEX,正是該條指令導致的溢位。

技巧五:基於JS日誌的漏洞分析技巧

有時在除錯IE漏洞時,我們需要樣本中的JS程式碼的執行先後情況,透過新增一些數學函式,比如math.atan2、math.asin等,還可以使用其它函式,然後透過對jscript!Js::Math::Atan(或者其它函式)下斷點來輸出log,以方便分析者觀察執行情況。比如poc中有如下js,其中的math.atan2是我們新增的:

#!cpp
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);

透過設定以下斷點:

#!bash
bu jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g"

執行後在windbg上的輸出視窗就會看到:

#!bash
[*] Creating object button...
[*] Assigning data to title...
[*] Let's AppendChild

技巧六:透過虛擬機器快照來固定堆地址

最早聽到這個方法,是instruder在QQ群裡面提到的。由於在分析漏洞時,尤其是堆漏洞時,每個重新載入執行時,分配的堆地址都是固定,無論是分析還是寫文件,都不太利用於我們分析和描述。因此我們可以先將程式除錯已經完成堆分配的某個地址,然後將其儲存為虛擬機器快照,等我們需要再重新開始除錯時,可透過恢復先前儲存的快照來重新除錯,那麼此時的堆地址跟之前分析的地址都是固定的。

技巧七:監控堆分配釋放動作來分析UAF、double free漏洞

以之前分配libpng某個double free漏洞的分析為例,主要是透過對釋放函式free進行監控並輸出記錄來分析的。程式崩潰時的棧回溯如下:

NewImage

重新用windbg載入,透過監控堆塊的釋放過程,可以發現它在對其中某堆塊進行了雙重釋放,先下斷:

#!bash
bu 3440D279 ".if(1){.echo EnterVulnFunc;gc}"
bu 6e264b6c ".if(1){.echo Free heap block; dd esp l4;gc}"

輸出結果:

#!bash
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漏洞的物件,如下圖所示。

NewImage

技巧八:基於汙點追蹤的分析方法

汙點追蹤理論挺好的,但成品往往不容樂觀。汙點追蹤猶如“七傷拳”一般,“先傷己,再傷人”,開發汙點追蹤工具,不僅費時費力,而且開發完成後,執行比較大的工具往往需要執行很長時間,比如IE、Adobe等軟體,有時甚至需要整整一天的時間,這種一般是在除錯分析不方便的時候才使用的,主要針對檔案格式漏洞。另外,你也可利用pin等動態插樁框架開發出動態分析工具,針對特定函式掛鉤,比如堆分配與釋放函式,也可以用它來實現快速分析。

NewImage

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章