使用免费工具进行逆向和利用:第 10 部分

  • Comments Off on 使用免费工具进行逆向和利用:第 10 部分
  • 7 views
  • A+

原文信息

地址:https://www.coresecurity.com/core-labs/articles/reversing-and-exploiting-free-tools-part-10

作者:Ricardo Narvaja


在本系列的前几部分中,我们介绍了一些基本的利用和逆向示例。现在我们将向前迈出一步,逐渐添加我们将发现的各种保护和缓解措施。

什么是缓解措施?

多年来,新系统增加了新的通用防御方法,顾名思义,这些方法并不妨碍,但减轻或增加了开发的难度

我们将做的第一件事是一个简单的32位练习,它类似于我们在前面的部分中看到的练习,但是增加了启用DEP的保护。从那里,我们将在64位中进行类似的练习,在这里,所有进程都启用了DEP。这样,我们将逐渐学会如何管理不同类型的情况。

什么是DEP?

在微软网站上可以找到DEP的一个极好的定义:

关键是,如果一个进程启用了DEP,那么用于输入或管理数据(如堆或栈)的内存区域就没有被执行的权限。这意味着他们最多只能拥有读写权限。

另一方面,只有可执行文件和动态链接库的代码段有执行权限,而这些代码段又没有写权限。

这样,我们就很难执行自己的代码。如果我们将自己的代码存储在我们想要执行的缓冲区中,然后当我们尝试这样做时,它会给出一个没有执行权限的错误,我们将无法像在前面的示例中那样利用它。

换句话说,如果一个程序在启动时启用了DEP,这些就是我们的权限:

```
SECTIONS THAT HANDLE DATA = R or RW (non-executable, read and / or write-only)

CODE SECTIONS = X (executables)
```

虽然没有启用DEP的程序会为我们提供执行权限:

```
SECTIONS THAT HANDLE DATA = RX o RWX (executables + read and / or write)

CODE SECTIONS = X (executables)
```

还值得注意的是,在64位Windows系统上,运行在WoW64上的32位进程的行为与它们在32位系统上的行为相同。

Wow64是什么?

根据维基百科,wow 64(Windows 64位上的Windows 32位)是“Windows操作系统的一个子系统,能够在64位Windows上运行32位应用程序。它包含在所有64位版本的Windows中—包括Windows XP Professional x64 Edition、IA-64和x64版本的Windows Server 2003,以及x64版本的Windows Vista、Windows Server 2008、Windows 7、Windows 8、Windows Server 2012、Windows 8.1、Windows 10、Windows Server 2016、Windows Server 2019和Windows 11,以及ARM64版本的Windows 10、Windows 11和Windows Server 2022。在Windows Server 2008 R2服务器核心中,它是一个可选组件。它在Windows纳米服务器变体中不可用。WoW64旨在解决32位Windows和64位Windows之间的许多差异,尤其是涉及到Windows本身的结构变化。”

简单地说,这就像在64位系统中运行32位系统的模拟器。如果WoW64子系统不存在于64位系统中,我们将无法在其上运行32位应用程序。

所有这些对用户来说都是透明的,用户可以执行32位或64位进程,而不必执行任何额外的步骤。他们只需要双击一个可执行文件,系统会检测它是否是64位的,在这种情况下,它会直接执行它。如果它被检测为32位,系统将把它传递给WoW64子系统,让它运行。

这意味着我们在本系列前几部分看到的所有以32位编译的可执行文件实际上都运行在WoW64子系统上。

关于DEP,取决于一个程序在有或没有DEP的情况下是如何编译的,在32位或WOW64中编译的程序将分别在有或没有DEP的情况下运行。对于用64位代码编译的程序,无论它们是如何编译的,它们都将始终启用DEP。

当然,这只是默认的系统配置,可以更改。例如,在32位系统中,有可能更改所有进程以启用DEP,就像64位系统的默认配置一样。然而,出于这些练习的目的,我们将总是在默认情况下引用每个系统及其选项。

除此之外,还有一个Windows函数,程序可以用它在32位进程中运行时激活DEP。

什么是SetDEPProcessPolicy?

为了直接从源代码中得到定义,让我们参考文档。据微软称,SetProcessDEPPolicy“为32位进程更改数据执行阻止(DEP)和DEP-ATL thunk模拟设置。”我们可以看到下面的语法和参数:

问题是该功能通常仅用于启用DEP。由于只有通过相同的功能启用时,它才用于禁用DEP,因此不能使用SetProcessDEPPolicy禁用通过另一种方法启用的DEP。

由于一个进程有许多不同的可能性启用或不启用DEP,仅仅静态地查看一个可执行文件是否用DEP编译并不足以知道它在运行时是否会最终启用。

因此,最好的方法是使用名为进程浏览器的工具,它来自微软。

下载后,您应该有两个版本选项:

在本练习中,让我们以管理员权限运行64位版本。这将显示具有64位Windows系统的机器上的所有进程,无论它们是64位还是Wow64。

在上面的截图中,我们可以看到DEP专栏。让我们通过添加图像类型列来看看64位进程是否支持DEP。

这可以通过右键单击,selecting中的select columns来完成。

添加此列后,我们可以看到在WoW64中运行的32位进程在这种情况下启用了DEP。但是,如果我们运行一个我们在本系列的前几期中完成的练习呢?

previous exercises

让我们在Process Explorer中看看这个进程。

process explorer

这个练习是用32位编译的,没有DEP,所以它将在没有启用DEP的情况下运行。

我们将选择在标记为PARTE 2的文件夹中看到的第一个练习

ABO1

解压缩文件将需要您输入密码。它是小写字母a。一旦完成,我们可以看到有以下两个练习。

decompressed file

为32位和64位编译了相同的练习。编译后的32位版本位于发布文件夹中。编译后的64位版本位于x64文件夹中。

两个文件的源代码是相同的。

console application 9 source code

让我们使用进程资源管理器运行32位版本:

我们可以看到这个练习已经让DEP永久启用了。如果我们运行64位版本,它也启用了DEP。

所以我们的下一个目标是试图绕过DEP。为了实现这一点,我们必须研究称为ROP的技术。

什么是ROP?

根据OSIRIS实验室的说法,代表面向返回编程的ROP“是将汇编的小片段与堆栈控制链接在一起的想法,以使程序做更复杂的事情。”

换句话说,这是一种在进程的可执行文件或动态链接库中查找小代码的技术,使用同一个进程的代码段,也就是DEP允许的代码段,因为它们被标记为X或可执行文件。

这些代码片段被称为GADGETS。

什么是ROP GADGETS?

ROP gadgets是简短的指令序列,一旦组合起来,就可以执行任意数量的任务或代码。ROP gadgets通常以ret指令结束,以便组合多个序列。

gadgets可以以RET (C3)或RET C2 (RETN康斯特)结尾。在一些复杂的情况下,它们甚至可以以CALL或JMP结尾。

对于这个例子,让我们让我们的gadgets构建一个对诸如VirtualProtect或VirtualAlloc之类的Windows函数的调用,这将允许我们向最初没有执行权限的数据部分授予执行权限。或者,它将允许我们创建一个具有读、写和执行权限的新部分,在那里我们复制代码,然后跳转到执行它。

因此,如果我们有一个启用DEP的流程:

```
SECTIONS THAT HANDLE DATA = R or RW (non-executables, read and / or write-only)

CODE SECTIONS = X (executables)
```

我们将能够将X(执行)添加到内存的某个部分。

```
SOME SECTION THAT HANDLES DATA = RX or RWX (we added execution already had read and / or write)

CODE SECTIONS = X (executables)
```

换句话说,绕过DEP的方法不是在整个过程中禁用它(这实际上是不可能的),而是向处理数据的内存的某个部分添加执行权限,以便在那里执行我们的代码。

这个想法是,由于我们控制并溢出堆栈,我们可以跳到任何我们想要的方向。因此,我们将跳转到一个制作POP ECX-RET的gadgets,而不是像以前那样跳转到CALL ESP、CALL EAX或直接跳转到执行我们代码的代码。

这将执行POP ECX,它将把存储在我们的受控堆栈中的值复制到ECX。然后RET会让我们跳到下一个gadgets,我们也控制它,因为它会低于复制到ECX的值。

这是链接gadgets执行的过程。执行完第一个gadgets后,链将继续执行第二个gadgets,以此类推。每个gadgets都将我们需要的值移动到每个寄存器中,以构建对VirtualAlloc或VirtualProtect函数的调用。

显然,这只是链接现有gadgets的一个例子。可能有些时候,我们会在模块中找到我们需要的gadgets,这些gadgets没有随机化,并且有固定的跳转地址。或者它可能需要补充地址泄漏技术,这将使我们能够获得模块地址,以避免ASLR。有时候,这将会更加困难,你必须深入挖掘。

这就是为什么机械钻速技术需要不断练习的原因。根据环境的不同,会有很多变化,漏洞利用编写器的成功在很大程度上取决于能否执行成功的ROP。

用GHIDRA中的DEP逆向32位可执行文件

您现在可以升级到最新版本:

让我们删除以前的项目并创建一个新项目。

然后我们将可执行文件拖放到创建的项目中。

一旦它被解析,我们就可以尝试给PDB加载符号。然而,它告诉我们我们已经加载了它,所以我们不需要再次加载它们。

接下来,我们将看到main函数。

我们可以使用内置的函数搜索引擎来查找它,然后双击它进入函数。

我们可以在图形模式下看到它。

这个函数是一个单独的块,我们可以看到这里什么都没有。只有对MessageBoxA的调用,然后是对函数f的调用,这就是溢出的地方。

我们可以看到,它实际上与我们已经看到的练习非常相似。虽然有一些不同,但它本质上是对VirtualAlloc的调用,以在内存中保留给定数量的字节,这是由传递给它的长度参数决定的。

如果我们右键单击长度变量,我们可以看到它在哪里被使用。

它标记使用它的位置、写入时间(WRITE)和读取它的值的时间(read)。

我们看到,第一次访问它时,它会保存值0x64,然后还有两次访问它来使用该值。第一次作为VirtualAlloc的参数访问时。接下来,它在memcpy中用于将数据复制到分配的0x64内存中,通过使用gets进入缓冲区。

references to length

它还显示了对length的引用。

随着gets的使用,我们将会有一个溢出,因为它会不受控制地复制。

memcpy最初没有问题,因为即使输入缓冲区大于0x64位(因为它只将0x64字节复制到大小= 0x64的缓冲区),也不会溢出。

只要要复制的大小等于目标缓冲区的大小,在这种情况下就不会有溢出,因为它不会复制超过缓冲区的大小。

接下来,我们可以反编译,因为GHIDRA有一个反编译程序。

我们可以看到这个函数几乎是完全反编译的,并说明了我们上面讨论的内容:对VirtualAlloc的调用保留了100个字节(0x64h)。唯一不同的是,它在这里没有使用长度变量,而是直接用常量100代替。

但是,溢出发生在gets函数中。memcpy函数正确地用于大小为100字节的_Dst。复制的字节量也是100字节,所以不会溢出。

我们必须复制多少字节才能踩到返回地址?

我们可以看到有三个变量:buf、区域和长度。我们还看到长度位于buf以下。虽然在memcpy中,它最初只复制了100个,但它可以通过溢出buf和粉碎长度值来复制更多。

下面是保存的EBP和返回地址,每个地址由4个字节组成。

我们将类型更改为DWORDS,然后重命名它。

现在清楚地显示出来了。

和GHIDRA一样,HORIZON是函数开始时ESP的值。它的正下方是返回地址,偏移量为0x0。此外,上面还有0x40c字节。

在IDA中,地平线是EBP在序言中设定的价值。如果我们右击并选择ARRAY,我们会看到在RETURN ADDRESS之前要填充的数据是1036字节。

变量buf将位于IDA中偏移量= - 0x408处,因为引用来自EBP。

但是GHIDRA也在使用ESP作为参考,所以请记住GHIDRA具有这种双重性。

在变量的用法中,它显示EBP-0x408。像IDA一样把EBP作为参考,但是在函数的定义和距离上,它给我们显示了0x40C。这是功能开始时到ESP的距离。

我们必须考虑到这一点,以避免混淆。

如果我们把要填充的变量的长度加起来,直到返回地址之前,那么就是:

0x400 + 4 +4 +4 = 1036

因此,如果一个人不感到困惑并知道他们在做什么,他们将在IDA和GHIDRA得到同样的结果。

如果我们没有激活DEP,利用这个练习的脚本如下:

如果我们在x64dbg中打开它,我们可以使用VIEW-MODULES来定位可执行文件。

我们需要在可执行文件中搜索并找到一个CALL EAX命令。由于EAX仍然使用buf的地址,我们需要跳到那里执行。

call eax

我们可以通过使用任何一个CALL EAX命令,将SHELLCODE放在缓冲区的开头(如果没有激活DEP的话),完美地跳转到执行。

让我们试着运行它。我们将附加x64dbg,并在从MessageBoxA返回时放置一个断点,这将确保程序停在那里。

用F7追踪进入函数f。

当缓冲区被shellcode和零填充完成时,标志的值为零,这使得它进入memcpy或不进入memcpy。这意味着它会跳到memcpy之上。

trace with f7

jump above memcpy

当我们到达返回地址时,它指向CALL EAX。

如果我们继续用f7追踪,CALL EAX将被执行。这是因为它是属于具有执行权限的可执行模块的代码部分的代码。

如果我们继续用f7跟踪,我们将到达shellcode。

即使我们想继续执行这段代码,它也会在这里崩溃,因为堆栈没有执行权限。

所以看起来游戏结束了。它不会执行shellcode,它会关闭,这就是DEP保护的全部意义。

然而,我们看到CALL EAX被处决了。因为它属于代码段,所以它有执行权限,否则程序本身无法执行。

练习MINIROP

所有这些都是机械钻速的基础。

了解这一点后,我们希望编写一个脚本,其中包含一个设置这些值的示例迷你ROP:

EAX = 0x 41414141 ECX = 0x 42424242 EBP = 0x 43434343

我们可以找到三个gadgets:

  1. POP EAX-RET
  2. POP ECX-RET
  3. POP EBP -RET

或者我们也可以找到一个设置多个寄存器的gadgets,例如:

POP ECX-POP EAX-RET

让我们看看我们在代码中发现了什么。

我们将使用一个免费工具来查找将模块的所有gadgets存储在一个文件中的gadgets。

这个工具叫做RP ++,这里有:

https://drive.google.com/open?id=1M3LeiU5WzbsEqSnSwrEJupKCH_2wORnV

让我们将可执行文件放在同一个文件夹中,这样会更容易。

rp-win-x86.exe --file = ConsoleApplication9.exe --raw = x86 --rop = 4> pepe.txt I

我们必须使用32位可执行文件(rp-win-x86.exe)并寻找32位代码(raw = x86)。我们将找到最多四个指令(rop = 4)并保存在一个文件(pepe.txt)中。

让我们在NOTEPAD++中搜索POP ECX的所有实例,看看会出现什么。

现在我们有了gadgets:

  • 0x00004828: pop eax; pop ebp; ret; (1 found)
  • 0x000033e3: pop eax; pop ebp; ret; (1 found)
  • 0x00005545: pop eax; pop ebp; ret; (1 found)
  • 0x000108d9: pop eax; pop ebp; ret; (1 found)
  • 0x00011126: pop eax; pop ebp; ret; (1 found)
  • 0x000110ad: pop eax; pop ebp; ret; (1 found)

另外:

  • 0x00004461: pop ecx; pop ebp; ret; (1 found)
  • 0x0000447c: pop ecx; pop ebp; ret; (1 found)
  • 0x00004c65: pop ecx; pop ebp; ret; (1 found)
  • 0x00004e2b: pop ecx; pop ebp; ret; (1 found)
  • 0x00007788: pop ecx; pop ebp; ret; (1 found)
  • 0x000069d4: pop ecx; pop ebp; ret; (1 found)
  • 0x00007648: pop ecx; pop ebp; ret; (1 found)
  • 0x00009646: pop ecx; pop ebp; ret; (1 found)

以及:

  • 0x00000718: pop ecx; ret; (1 found)
  • 0x000005dd: pop ecx; ret; (1 found)
  • 0x00001820: pop ecx; ret; (1 found)
  • 0x00002849: pop ecx; ret; (1 found)
  • 0x00003d06: pop ecx; ret; (1 found)
  • 0x000038da: pop ecx; ret; (1 found)
  • 0x000044b9: pop ecx; ret; (1 found)
  • 0x00004592: pop ecx; ret; (1 found)
  • 0x000048c7: pop ecx; ret; (1 found)
  • 0x000049f2: pop ecx; ret; (1 found)
  • 0x00004a4d: pop ecx; ret; (1 found)
  • 0x00004d55: pop ecx; ret; (1 found)
  • 0x00004dfc: pop ecx; ret; (1 found)

我们可以用这些gadgets来测试rop。但是,问题是该工具返回文件偏移量,因为它是静态打开文件的。

幸运的是,在本课程的前面部分,我们已经看到了如何根据文件偏移量计算虚拟内存地址。

如果我们想用x64dbg快速完成,选择GOTO-FILE OFFSET,输入gadgets的FILE OFFSET,得到虚拟地址。

让我们试着计算一下。我们从rabin2中看到,代码段从磁盘上的0x400开始,因此从文件偏移量- 0x400的值中减去,我们就得到磁盘上第一段开始处的偏移量。添加代码段在内存中开始的位置(图像基数加上头0x401000的大小)应该会给出虚拟地址。

0x4592- 0x400 + 0x401000 = 0x405192

我们希望我们的ROP设置这些值:

EAX = 0x41414141 ECX = 42424242 EBP = 43434343 I will

我们还将使用另一个gadgets

```
0x00005545: pop eax; pop ebp; ret; (1 found)

virtual address = 0x406145

hex (0x5545- 0x400 + 0x401000)

'0x406145'
```

该脚本可以在下图中看到:

在那里,我们看到两个gadgets之间穿插着数值,它将与持久性有机污染物一起读取数值,并转移到寄存器中。

让我们运行它,看看我们是否得到了我们想要的。

当我们到达RET并能看到我们的ROP时,我们可以用f7追踪它。

第一个gadgets是POP ECX-RET

它将值0x42424242移动到带有POP ECX的ECX。当我们到达ret指令时,下一个gadgets将被执行。

现在正在执行第二个GADGETs。

second gadget

它将把0x42424242移动到EAX,把0x43434343移动到EBP。

我们可以看到,我们设法链接了几个GADGETS,并将我们想要的值放在我们需要它们的寄存器中。

如果我们愿意,我们可以继续在下面放更多的小玩意,做不同的事情。

这是一个很好的停止点。在第11部分,我们将看到完整的ROP绕过这个练习的DEP。

相关推荐: 关于大火的Print Spooler漏洞的深度分析

本篇文章几天之前就整理好了,一直没有时间整理,往平台上发。 前言 前一段时间,Windows Print Spooler的CVE-2021-1675和CVE-2021-34527两个洞被炒的沸沸扬扬。漏洞的利用姿势也是越来越多样,由刚开始的本地提权演变为后来的…