在这篇文章中,我们会解决 Protostar 网站中堆溢出练习的问题。我们会主要
关注堆内存溢出的原因和方法。
堆内存可以被看做为另一个内存区域。它是通过如 dlmalloc, jemalloc, ptmalloc 等各类内存分配器分配的。在 Linux 系统中,malloc 调用是由 mmap 和 brk 系统调用组合而来的。在底层,内存分配是由内核完成的。然而,内存分配的大小以及时机是由内存分配器决定的。
在这个文章中使用的虚拟机可以从这里下载。
为了让挑战更有趣,我尝试了从每个权限级别使用不同的方法。这也意味着这个挑战并可以做的不仅仅是改写变量或者函数指针。
如下的代码是用于向我们展示如何解决 Heap 0 问题,本关的目标是重写 winner 函数的指针。
从代码中我们可以看出,64 字节的堆内存被用于 struct 的名字,4 个字节用于 struct fp。而 nowinner 的函数指针则被放在了堆内存中。接着,我们的输入会通过有漏洞的 strcpy 函数拷入到 struct 数据的 name 变量中。所以,我们可以放心地假设,在64字节之后的空间中我们可以将函数指针重写到 nowinner 函数中。
当我们进一步逆向主程序并分析后,发现 malloc 被用于分配64字节的空间。然而,它多分配了8个字节(0x00000049),其中前4个字节表示 prev_size 数据(如果前一个块(chunk)的空间是空的则为前一个块空间的大小,否则为前一个块的用户数据)。另外4个字节的用于表示被分配块的大小,其中3LSB的bit用于不同的标志(PREV_INUSE [0x1], IS_MAPPED [0x2], NON_MAIN_ARENA [0x4]),这些标志是用于描述前一个块空间的。
在接下来的截屏中可以看到我们的输入是如何被放置于堆内存中的。由于这是处于最高位置的块,因此 prev_inuse 比特位在块大小的字段中被设为1(72 字节 = 0x00000048 + 0x1)。要在 gdb 中获取堆内存的起始地址,你可以使用 info proc mapping 命令,接着我们可以检查字节数为x/64wx 0x804a0000。
我们还可以看到 nowinner 函数的指针指向 0x804a050,而在传入了64个字节之后,我们就能够重写指针到 0x804a044 了。所以,我们需要多写2个字(word)大小的内容,然后重写 nowinner 函数的指针到 winner 函数的地址来通过这关。
我们进一步分析发现,在栈中,指向输入的指针就放在函数地址之后。因此我们可以直接改写 nowinner 函数的指针到系统函数,来使运行代码。
在这关有嵌入的 malloc 调用,接着,还有有漏洞的 strcpy 函数用于将我们的输入拷贝至 struct 中。
我们在每个 malloc 和 strcpy 之前放置断点来动态分析,同时也开始分析堆中的内容。
可以看到,每次 strcpy 之后堆的结构如图所示,从此我们可以推断在第一个 malloc 调用时,字段 *name 的优先级和内存地址被放到了栈上,然后在I2的 malloc 调用完成的时候发生了同样的事情。因为这个程序使用了有漏洞的 strcpy 函数,这也意味着我们可以写任意的数据到任何可写的位置。
如图所示,我们使用第一个 strcpy 函数来改写地址0x804a038并放了一个 GOT 进去,通过第二个 strcpy 函数来改写地址0x42424242为 winner 函数的 PLT 地址,最终通过了本关。
要在此情况下运行代码,我们使用了我们20字节的 shell 代码并通过第一个 strcpy 修改了0x804a038地址内容,放置了 GOT (译者:不明白GOT是什么)。这样我们可以通过第二个 strcpy 放置 GOT,从而能够修改地址来运行我们的 shell 代码。
通过查看源码,我们可以发现这个应用对三条输入的用户指令有反应。
Auth:复制堆上的一些数据
Service:使用了 strdup,这个指令内部使用了 malloc 调用来复制堆上的一些数据,然后存在 eax 寄存器中。
Reset:释放 Auth 对象
Login:用于检查我们是否登录
这个程序有多个漏洞。首先,有多个变量的名字为 Auth。其次,这个程序在 Auth 对象被释放后依旧使用了它。
可以看到在传入了 Auth 和 service 命令及一些数据后,堆的状态。现在我们知道 strdup 的结果存在 eax 寄存器中,而且检查是在解析了 eax 中的地址往后的 0x20 字节处的内容后进行的(mov eax, DWORD PTR [eax+0x20]; test eax,eax
)。所以,这意味着如果我们能够在 service 命令中传入一个长于 0x20 个字节的字符串,我们就可以重写 auth 变量并通过本关了。
我们也可以通过向 service 命令赋一些数据,来最终在堆上多向 auth 分配一个块重写这个变量。
我们可以看到,我们能够通过使用 service 命令来两次赋值数据,从而改写 auth 变量。我们也可以在命令行里尝试。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课