DEP(数据执行保护)是一种内存保护功能,允许系统将内存页标记为不可执行。ROP(面向返回的编程)是一种利用技术,允许攻击者在启用DEP等保护的情况下执行shellcode。在这篇文章中,我们将介绍应用程序的逆向过程,以发现缓冲区溢出漏洞,并开发用于绕过DEP的ROP小工具链(Gadget Chain)。
我们将在开发过程中使用以下工具:QuoteDB、TCPView(一个查看端口和线程的小工具,只要木马在内存中运行,一定会打开某个端口,只要黑客进入你的电脑,就有新的线程)、IDA Freeware、WinDbg(在windows平台下,强大的用户态和内核态调试工具)和rp++。
QuoteDB是一个设计上易受攻击的应用程序,创建它是为了实践逆向工程并利用它进行开发。如下图所示,该应用程序正在侦听端口3700上的网络连接:
我们已经使用TCPView确认程序确实在监听端口3700。
现在我们需要对应用程序进行逆向工程,看看它是如何处理网络连接的。accept函数用于允许指定端口上的传入连接,然后进程创建一个运行“handle_connection”例程的新线程,如下所示:
recv函数用于从连接的套接字接收数据:
我们已经开发了一个基本的Python脚本,它创建一个TCP套接字,并在端口3700上向远程服务器发送1000个“a”字符:
我们已将WinDbg附加到QuoteDB.exe进程,并列出了加载的模块,如下图所示。
我们可以使用“bp”命令在recv函数调用后放置断点,使用“bl”命令确认断点已成功设置:
recv函数返回后,EAX寄存器包含以十六进制接收的字节数:
缓冲区的前4个字节表示一个操作码,该操作码被移到EAX寄存器中,然后打印在命令行中:
下图显示了WinDbg中的printf调用,我们可以观察到第三个参数(=Opcode)由4个“A”字符组成:
该进程显示源IP地址、源端口、缓冲区长度和十进制的操作码:
应用程序从Opcode中减去0x384(十进制900),并将结果与4进行比较。这是一个带有5个示例的开关,也显示在下图中。
EAX寄存器大于4,执行流被重定向到默认情况,该情况调用“log_bad_request”函数:
上述函数包含缓冲区溢出漏洞,如下图所示,可执行文件在堆栈上分配0x818(2072)字节,用0初始化缓冲区,并在不检查边界的情况下将有效负载复制到此缓冲区:
发生溢出是因为要复制的字符数(0x4000)大于缓冲区的大小,并且可能会重写返回地址:
我们选择发送3000个“A”字符以利用该漏洞。如下所示,返回地址在堆栈上被重写,程序因此崩溃:
我们使用了“msf-pattern_create”命令来生成一个唯一的模式,该模式将为我们提供偏移量。
应用程序在不同的地址崩溃,该地址用于使用“msf-pattern_offset”命令确定精确的偏移量:
我们修改了概念证明,以包括上述偏移量。在正确的地址崩溃后,ESP寄存器指向我们控制的缓冲区的最后一部分:
我们使用了narly WinDbg扩展来显示加载的模块及其内存保护,下图显示了该可执行文件是在启用ASLR和DEP保护的情况下编译的。
Windows Defender Exploit Guard可以用来启用/禁用ASLR。我们需要进入“Exploit protection settings”,选择“Program settings”页签,点击“Add Program to custom”,选择“Choose exact file path”选项:
我们想通过发送从“\x00”到“\xFF”的所有字节并确定它们如何写入堆栈来找出哪些字符被认为是“不适合”的:
如下图所示,没有不适合的字符,不过为了研究,我们将“\x00”视为不适合字符,因为它通常是不适合字符。正因为如此,漏洞开发过程稍微复杂一些,但它可能更容易适应其他应用程序。
我们使用rp++工具从“SysWOW64\kernel32.dll”模块中提取ROP小工具,因为ASLR是禁用的,所以我们可以选择任何提供必要ROP小工具的DLL,但是,我们将在以后的文章中看到应用程序泄漏特定DLL中的地址。我们已将小工具中的最大指令数设置为5:
由于DEP保护,堆栈不再是可执行的,我们需要找到执行shellcode的方法。我们可以使用VirtualAlloc、VirtualProtect和WriteProcessMemory等API来绕过DEP。VirtualAlloc函数用于保留、提交或更改进程地址空间中页面的状态。该函数有4个参数:
lpAddressdwSizeflAllocationTypeflProtect
我们的目的是将flAllocationType参数设置为0x1000(MEM_COMMIT),将flProtect设置为0x40(PAGE_EXECUTE_READWRITE)。我们需要在堆栈上创建以下框架:
VirtualAlloc addressReturn address (Shellcode address)lpAddress (Shellcode address)dwSize (0x1)flAllocationType (0x1000)flProtect (0x40)
我们为每个元素分配了一个特定的值,需要在运行时使用正确的值对其进行修改。
如下图所示,可以在ESP寄存器的固定偏移处找到框架:
kernel32.dll模块的起始地址可以使用WinDbg来标识。所有ROP小工具的地址必须使用该值而不是“ROP.txt”文件中的加载地址来计算:
首先,我们需要找到一个保存ESP寄存器值的ROP小工具。我们确定了一个将ESP寄存器复制到ESI寄存器的寄存器:
我们修改了Python脚本,以包含kernel32地址和上述ROP小工具偏移量,如下所示:
我们已经成功地将执行流程重定向到我们的第一个ROP小工具,接着将其他ROP小工具链接在一起,因为ESP仍然指向我们的缓冲区:
现在我们需要找到从ESI寄存器中减去0x1C的方法。然而,由于缺少涉及使用ESI寄存器进行计算的ROP小工具,我们找到了一个将ESI寄存器复制到EAX中的ROP小工具。唯一的问题是ESI也被“POP ESI”指令修改,但是,它不会影响我们的利用:
在许多ROP小工具中发现的另一个寄存器是ECX。我们已经确定了一个ROP小工具,它从堆栈中弹出一个值到ECX寄存器中,另一个小工具将EAX和ECX寄存器加在一起。加上负值等于减去相同的正值:
通过在之前的EAX值上添加-0x1C (= ECX)值,EAX指向VirtualAlloc框架:
因为EAX在任何计算中都很有用,所以我们需要在执行任何其他操作之前找到保存它的方法。我们发现了一个ROP小工具,它将EAX寄存器复制到ECX中,ECX将用于修改框架中的值。事实上,EAX也被这个ROP小工具修改了,但这并不影响我们的利用:
我们修改后的概念证明如下图所示。“junk”值对堆栈对齐很有用,对应于“POP reg”和“retn4”指令。
再次运行Python脚本后,我们可以观察到ECX寄存器的值与之前的EAX寄存器相同,并指向VirtualAlloc框架:
IAT(导入地址表)包含指向由其他DLL导出的函数的指针。例如,kernel32.dll在VirtualAlloc的IAT中有一个条目,即使VirtualAlloc实际地址发生变化,该条目也保持不变:
我们使用了“POP EAX”指令将VirtualAlloc IAT复制到EAX寄存器中,需要对其进行解除引用才能获得VirtualAlloc地址,如下所示:
在更新Python脚本并再次运行之后,我们成功地获得了EAX中的VirtualAlloc地址:
因为ECX仍然指向VirtualAlloc框架,所以我们需要一个包含“MOV [ECX], EAX”的ROP小工具,以便使用VirtualAlloc地址更新第一个框架值:
我们需要找到一种方法来修改ECX寄存器以指向下一个框架值。“INC ECX”指令用于将1添加到ECX寄存器,我们已使用了其中的4个:
如下图所示,ECX指向下一个需要修改的元素:
第二个框架值对应于shellcode地址。第一个ROP小工具将ECX寄存器复制到EAX中。我们的想法是将shellcode放在有效载荷中ROP小工具之后,这将代表一个比当前更高的地址。我们已经从EAX寄存器中减去了一个负偏移量(-0x210),现在EAX指向一个可以用我们的shellcode填充的缓冲区:
使用以前的ROP小工具,我们更新了框架中的第二个值,现在看起来如下:
第三个骨架值(lpAddress)也应该等于shellcode地址。类似地,我们从EAX寄存器中减去了一个不同的偏移量(-0x20c),因为EAX增加了4。你可能会注意到,两次执行之间的堆栈地址是不同的,但偏移量保持不变:
第四个框架值(dwSize)应初始化为1,由于我们认为“\x00”是一个不适合字符,我们不能只将所需的值放在堆栈上,因为它包含NULL字节。我们已使用“NEG EAX”指令让-1 = 0xFFFFFFFF 无效,并获得所需值:
第五个框架值(flAllocationType)应设置为0x1000。我们需要找到两个和为0x1000的十六进制数,我们考虑number1=0x88888888。通过简单的数学运算,我们可以确定第二个数字必须是0x77778778,如下所示:
我们使用“POP reg”指令将这两个数字复制到EAX和ESI中,并使用另一个ROP小工具执行添加操作,如下图所示。
我们几乎完成的VirtualAlloc框架如下所示:
最后一个框架值(flProtect)应初始化为0x40。我们已经提供了获得所需结果的必要步骤:
最后,我们需要找到一种使用修改后的参数执行VirtualAlloc函数的方法。指向最后一个框架值的ECX寄存器被复制到EAX寄存器中,EAX寄存器需要减去0x14(框架中的6个元素)才能指向VirtualAlloc框架的第一个值。“xchg”指令用于交换EAX寄存器和ESP寄存器的内容,从而执行VirtualAlloc函数:
ROP小工具链的最后一部分如下所示:
执行VirtualAlloc API后,我们可以看到缓冲区现在是可执行的:
我们可以确定shellcode地址和最后一个ROP小工具之间的距离是0x9C字节。
我们添加了0x9C填充字节,然后添加了一个包含NOP指令的伪shellcode,以确认偏移量是否正确:
我们已经使用msfvenom生成了一个逆向shell,并成功地执行了攻击。通过将多个执行VirtualAlloc调用的ROP小工具链接在一起,并将包含shellcode的内存页作为可执行文件来绕过。
参考及来源:https://cybergeeks.tech/a-step-by-step-introduction-to-the-use-of-rop-gadgets-to-bypass-dep/