手把手教你如何用 ROP 绕过数据执行保护
2023-2-17 12:0:0 Author: www.4hou.com(查看原文) 阅读量:18 收藏

DEP(数据执行保护)是一种内存保护功能,允许系统将内存页标记为不可执行。ROP(面向返回的编程)是一种利用技术,允许攻击者在启用DEP等保护的情况下执行shellcode。在这篇文章中,我们将介绍应用程序的逆向过程,以发现缓冲区溢出漏洞,并开发用于绕过DEP的ROP小工具链(Gadget Chain)。

我们将在开发过程中使用以下工具:QuoteDB、TCPView(一个查看端口和线程的小工具,只要木马在内存中运行,一定会打开某个端口,只要黑客进入你的电脑,就有新的线程)、IDA Freeware、WinDbg(在windows平台下,强大的用户态和内核态调试工具)和rp++。

QuoteDB是一个设计上易受攻击的应用程序,创建它是为了实践逆向工程并利用它进行开发。如下图所示,该应用程序正在侦听端口3700上的网络连接:

1.jpg

我们已经使用TCPView确认程序确实在监听端口3700。

2.jpg

现在我们需要对应用程序进行逆向工程,看看它是如何处理网络连接的。accept函数用于允许指定端口上的传入连接,然后进程创建一个运行“handle_connection”例程的新线程,如下所示:

3.jpg

recv函数用于从连接的套接字接收数据:

4.jpg

我们已经开发了一个基本的Python脚本,它创建一个TCP套接字,并在端口3700上向远程服务器发送1000个“a”字符:

5.jpg

我们已将WinDbg附加到QuoteDB.exe进程,并列出了加载的模块,如下图所示。

6.jpg

我们可以使用“bp”命令在recv函数调用后放置断点,使用“bl”命令确认断点已成功设置:

7.jpg

recv函数返回后,EAX寄存器包含以十六进制接收的字节数:

8.jpg

缓冲区的前4个字节表示一个操作码,该操作码被移到EAX寄存器中,然后打印在命令行中:

9.jpg

下图显示了WinDbg中的printf调用,我们可以观察到第三个参数(=Opcode)由4个“A”字符组成:

10.jpg

该进程显示源IP地址、源端口、缓冲区长度和十进制的操作码:

11.jpg

应用程序从Opcode中减去0x384(十进制900),并将结果与4进行比较。这是一个带有5个示例的开关,也显示在下图中。

12.jpg

EAX寄存器大于4,执行流被重定向到默认情况,该情况调用“log_bad_request”函数:

13.jpg

上述函数包含缓冲区溢出漏洞,如下图所示,可执行文件在堆栈上分配0x818(2072)字节,用0初始化缓冲区,并在不检查边界的情况下将有效负载复制到此缓冲区:

14.jpg

发生溢出是因为要复制的字符数(0x4000)大于缓冲区的大小,并且可能会重写返回地址:

15.jpg

我们选择发送3000个“A”字符以利用该漏洞。如下所示,返回地址在堆栈上被重写,程序因此崩溃:

16.jpg

17.jpg

我们使用了“msf-pattern_create”命令来生成一个唯一的模式,该模式将为我们提供偏移量。

18-1536x180.jpg

应用程序在不同的地址崩溃,该地址用于使用“msf-pattern_offset”命令确定精确的偏移量:

19.jpg

20.jpg

我们修改了概念证明,以包括上述偏移量。在正确的地址崩溃后,ESP寄存器指向我们控制的缓冲区的最后一部分:

21.jpg

22.jpg

我们使用了narly WinDbg扩展来显示加载的模块及其内存保护,下图显示了该可执行文件是在启用ASLR和DEP保护的情况下编译的。

23.jpg

Windows Defender Exploit Guard可以用来启用/禁用ASLR。我们需要进入“Exploit protection settings”,选择“Program settings”页签,点击“Add Program to custom”,选择“Choose exact file path”选项:

24.jpg

25.jpg

我们想通过发送从“\x00”到“\xFF”的所有字节并确定它们如何写入堆栈来找出哪些字符被认为是“不适合”的:

26-1.jpg

如下图所示,没有不适合的字符,不过为了研究,我们将“\x00”视为不适合字符,因为它通常是不适合字符。正因为如此,漏洞开发过程稍微复杂一些,但它可能更容易适应其他应用程序。

27.jpg

我们使用rp++工具从“SysWOW64\kernel32.dll”模块中提取ROP小工具,因为ASLR是禁用的,所以我们可以选择任何提供必要ROP小工具的DLL,但是,我们将在以后的文章中看到应用程序泄漏特定DLL中的地址。我们已将小工具中的最大指令数设置为5:

28.jpg

29.jpg

由于DEP保护,堆栈不再是可执行的,我们需要找到执行shellcode的方法。我们可以使用VirtualAlloc、VirtualProtect和WriteProcessMemory等API来绕过DEP。VirtualAlloc函数用于保留、提交或更改进程地址空间中页面的状态。该函数有4个参数:

lpAddress
dwSize
flAllocationType
flProtect

我们的目的是将flAllocationType参数设置为0x1000(MEM_COMMIT),将flProtect设置为0x40(PAGE_EXECUTE_READWRITE)。我们需要在堆栈上创建以下框架:

VirtualAlloc address
Return address (Shellcode address)
lpAddress (Shellcode address)
dwSize (0x1)
flAllocationType (0x1000)
flProtect (0x40)

我们为每个元素分配了一个特定的值,需要在运行时使用正确的值对其进行修改。

30-1.jpg

如下图所示,可以在ESP寄存器的固定偏移处找到框架:

31.jpg

kernel32.dll模块的起始地址可以使用WinDbg来标识。所有ROP小工具的地址必须使用该值而不是“ROP.txt”文件中的加载地址来计算:

32.jpg

首先,我们需要找到一个保存ESP寄存器值的ROP小工具。我们确定了一个将ESP寄存器复制到ESI寄存器的寄存器:

33.jpg

我们修改了Python脚本,以包含kernel32地址和上述ROP小工具偏移量,如下所示:

34-1.jpg

我们已经成功地将执行流程重定向到我们的第一个ROP小工具,接着将其他ROP小工具链接在一起,因为ESP仍然指向我们的缓冲区:

35.jpg

36.jpg

现在我们需要找到从ESI寄存器中减去0x1C的方法。然而,由于缺少涉及使用ESI寄存器进行计算的ROP小工具,我们找到了一个将ESI寄存器复制到EAX中的ROP小工具。唯一的问题是ESI也被“POP ESI”指令修改,但是,它不会影响我们的利用:

37.jpg

38.jpg

在许多ROP小工具中发现的另一个寄存器是ECX。我们已经确定了一个ROP小工具,它从堆栈中弹出一个值到ECX寄存器中,另一个小工具将EAX和ECX寄存器加在一起。加上负值等于减去相同的正值:

39.jpg

40.jpg

通过在之前的EAX值上添加-0x1C (= ECX)值,EAX指向VirtualAlloc框架:

41.jpg

因为EAX在任何计算中都很有用,所以我们需要在执行任何其他操作之前找到保存它的方法。我们发现了一个ROP小工具,它将EAX寄存器复制到ECX中,ECX将用于修改框架中的值。事实上,EAX也被这个ROP小工具修改了,但这并不影响我们的利用:

42.jpg

我们修改后的概念证明如下图所示。“junk”值对堆栈对齐很有用,对应于“POP reg”和“retn4”指令。

43.jpg

再次运行Python脚本后,我们可以观察到ECX寄存器的值与之前的EAX寄存器相同,并指向VirtualAlloc框架:

44.jpg

IAT(导入地址表)包含指向由其他DLL导出的函数的指针。例如,kernel32.dll在VirtualAlloc的IAT中有一个条目,即使VirtualAlloc实际地址发生变化,该条目也保持不变:

45.jpg

我们使用了“POP EAX”指令将VirtualAlloc IAT复制到EAX寄存器中,需要对其进行解除引用才能获得VirtualAlloc地址,如下所示:

46.jpg

47.jpg

在更新Python脚本并再次运行之后,我们成功地获得了EAX中的VirtualAlloc地址:

48.jpg

因为ECX仍然指向VirtualAlloc框架,所以我们需要一个包含“MOV [ECX], EAX”的ROP小工具,以便使用VirtualAlloc地址更新第一个框架值:

49.jpg

50.jpg

我们需要找到一种方法来修改ECX寄存器以指向下一个框架值。“INC ECX”指令用于将1添加到ECX寄存器,我们已使用了其中的4个:

51.jpg

52.jpg

如下图所示,ECX指向下一个需要修改的元素:

53.jpg

第二个框架值对应于shellcode地址。第一个ROP小工具将ECX寄存器复制到EAX中。我们的想法是将shellcode放在有效载荷中ROP小工具之后,这将代表一个比当前更高的地址。我们已经从EAX寄存器中减去了一个负偏移量(-0x210),现在EAX指向一个可以用我们的shellcode填充的缓冲区:

54.jpg

55.jpg

56.jpg

57.jpg

使用以前的ROP小工具,我们更新了框架中的第二个值,现在看起来如下:

58.jpg

第三个骨架值(lpAddress)也应该等于shellcode地址。类似地,我们从EAX寄存器中减去了一个不同的偏移量(-0x20c),因为EAX增加了4。你可能会注意到,两次执行之间的堆栈地址是不同的,但偏移量保持不变:

59.jpg

第四个框架值(dwSize)应初始化为1,由于我们认为“\x00”是一个不适合字符,我们不能只将所需的值放在堆栈上,因为它包含NULL字节。我们已使用“NEG EAX”指令让-1 = 0xFFFFFFFF 无效,并获得所需值:

60.jpg

61.jpg

第五个框架值(flAllocationType)应设置为0x1000。我们需要找到两个和为0x1000的十六进制数,我们考虑number1=0x88888888。通过简单的数学运算,我们可以确定第二个数字必须是0x77778778,如下所示:

62.jpg

我们使用“POP reg”指令将这两个数字复制到EAX和ESI中,并使用另一个ROP小工具执行添加操作,如下图所示。

63.jpg

我们几乎完成的VirtualAlloc框架如下所示:

64.jpg

最后一个框架值(flProtect)应初始化为0x40。我们已经提供了获得所需结果的必要步骤:

65.jpg

66.jpg

最后,我们需要找到一种使用修改后的参数执行VirtualAlloc函数的方法。指向最后一个框架值的ECX寄存器被复制到EAX寄存器中,EAX寄存器需要减去0x14(框架中的6个元素)才能指向VirtualAlloc框架的第一个值。“xchg”指令用于交换EAX寄存器和ESP寄存器的内容,从而执行VirtualAlloc函数:

67.jpg

ROP小工具链的最后一部分如下所示:

68.jpg

执行VirtualAlloc API后,我们可以看到缓冲区现在是可执行的:

69-1.jpg

70.jpg

我们可以确定shellcode地址和最后一个ROP小工具之间的距离是0x9C字节。

71.jpg

我们添加了0x9C填充字节,然后添加了一个包含NOP指令的伪shellcode,以确认偏移量是否正确:

72-1536x365.jpg

我们已经使用msfvenom生成了一个逆向shell,并成功地执行了攻击。通过将多个执行VirtualAlloc调用的ROP小工具链接在一起,并将包含shellcode的内存页作为可执行文件来绕过。

73.jpg

74.jpg

本文翻译自:https://cybergeeks.tech/a-step-by-step-introduction-to-the-use-of-rop-gadgets-to-bypass-dep/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/50OK
如有侵权请联系:admin#unsafe.sh