总结一些分析技巧是非常有必要的,针对不同的漏洞类型采取不同的分析思路和技巧,可以有效地提高分析速度
技巧一:通过页堆快速定位堆漏洞代码
页堆是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,它就是用于索引虚拟指令处理函数的。
通过快捷键Shift + F4对VulFunction下条件记录断点,记录每次调用VulFunction时对应的虚拟指令索引号,
设置好后重新加载AcroRd32.exe运行(不要关闭调试器否则前面设置的断点可能失效),打开poc.pdf运行后查看日志窗口:
导致漏洞的虚拟指令索引号为0x26,通过苹果官方提供的指令集https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html,可以查到字节码0x26对应的虚拟指令为MINDEX,正是该条指令导致的溢出。
技巧六:通过虚拟机快照来固定堆地址
最早听到这个方法,是instruder在QQ群里面提到的。由于在分析漏洞时,尤其是堆漏洞时,每个重新加载运行时,分配的堆地址都是固定,无论是分析还是写文档,都不太利用于我们分析和描述。因此我们可以先将程序调试已经完成堆分配的某个地址,然后将其保存为虚拟机快照,等我们需要再重新开始调试时,可通过恢复先前保存的快照来重新调试,那么此时的堆地址跟之前分析的地址都是固定的。