常用软件漏洞分析技巧

by Netfairy - 2015-06-19

总结一些分析技巧是非常有必要的,针对不同的漏洞类型采取不同的分析思路和技巧,可以有效地提高分析速度

技巧一:通过页堆快速定位堆漏洞代码

页堆是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=????????
    这已经是堆溢出后导致的内存读取异常了,不再是触发漏洞时最原始的场景了。因此开启页堆后,会更方便你去定位漏洞代码。
技巧三:基于堆分配记录定位整数溢出漏洞代码
    以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:          88c7298d64 -          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=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)}
技巧四:基于条件记录断点的漏洞分析技巧
    以CVE-2012-0774 Adobe Reader TrueType字体整数溢出漏洞为例,该漏洞主要是在解析TTF字体中的虚拟指令导致的溢出,为了确定导致溢出的是哪条虚拟指令,就可以通过设置条件记录断点来实现。 打开poc触发崩溃后,通过栈回溯定位漏洞函数,此处标记为VulFunction,它就是用于索引虚拟指令处理函数的。 

1.png

    通过快捷键Shift + F4对VulFunction下条件记录断点,记录每次调用VulFunction时对应的虚拟指令索引号,

2.png

    设置好后重新加载AcroRd32.exe运行(不要关闭调试器否则前面设置的断点可能失效),打开poc.pdf运行后查看日志窗口:

3.png

    导致漏洞的虚拟指令索引号为0x26,通过苹果官方提供的指令集https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html,可以查到字节码0x26对应的虚拟指令为MINDEX,正是该条指令导致的溢出。

技巧六:通过虚拟机快照来固定堆地址

    最早听到这个方法,是instruder在QQ群里面提到的。由于在分析漏洞时,尤其是堆漏洞时,每个重新加载运行时,分配的堆地址都是固定,无论是分析还是写文档,都不太利用于我们分析和描述。因此我们可以先将程序调试已经完成堆分配的某个地址,然后将其保存为虚拟机快照,等我们需要再重新开始调试时,可通过恢复先前保存的快照来重新调试,那么此时的堆地址跟之前分析的地址都是固定的。