6.1.9 pwn RHme3 Exploitation

下载文件

题目复现

这个题目给出了二进制文件和 libc。

$ file main.bin
main.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ec9db5ec0b8ad99b3b9b1b3b57e5536d1c615c8e, not stripped
$ checksec -f main.bin
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               10      main.bin
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.

64 位程序,保护措施除了 PIE 都开启了。

但其实这个程序并不能运行,它是一个线下赛的题目,会对做一些环境检查和处理,直接 nop 掉就好了:

|           0x004021ad      bf18264000     mov edi, 0x402618
|           0x004021b2      e87ceeffff     call sym.background_process
|           0x004021b7      bf39050000     mov edi, 0x539              ; 1337
|           0x004021bc      e85eefffff     call sym.serve_forever
|           0x004021c1      8945f8         mov dword [local_8h], eax
|           0x004021c4      8b45f8         mov eax, dword [local_8h]
|           0x004021c7      89c7           mov edi, eax
|           0x004021c9      e8c6f0ffff     call sym.set_io

最后把它运行起来:

题目解析

玩一下,一看就是堆利用的题目:

程序就是添加、删除、编辑和显示球员信息。但要注意的是在编辑和显示球员前,需要先选择球员,这一点很重要。

添加两个球员看看:

试着选中第一个球员,然后删除它:

接下来直接显示该球员信息:

奇怪的事情发生了,程序没有提醒我们球员不存在,而是直接读取了内存中的信息。

于是我们猜测,程序在 free 球员时没有将 select 的值置空,导致了 use-after-free 的问题。关于 UAF 已经在前面的章节中讲过了。

很明显,每个球员都是一个下面这样的结构体:

静态分析

先来看一下添加球员的过程,函数 sym.add_player

该函数会做一些基本的检查,如球员最大数量等,然后开始添加球员的过程。根据我们的分析,obj.players 应该是一个全局数组,用于存放所有球员的地址。

当球员添加完成后,就将其结构体地址添加到这个数组中。球员的选择过程就是通过这个数组完成的。

下面是选择球员的过程,函数 sym.select_player

对象 obj.selected 是一个全局变量,用于存放选择的球员编号。

选中球员之后,打印球员信息的操作就是通过从 obj.selected 中获取球员地址实现的。

下面是删除球员的过程,函数 sym.delete_player

该函数首先释放掉球员的名字,然后释放掉球员的结构体。却没有对 obj.selected 做任何修改,而该对象中存放的是选中球员的地址,这就存在一个逻辑漏洞,如果我们在释放球员之前选中该球员,则可以继续使用这个指针对内存进行操作,即 UAF 漏洞。

最后看一下显示球员信息的过程,函数 sym.show_player

在该函数中,也未检查选中球员是否还存在,这就导致了信息泄露。

函数 sym.edit_player 可以调用函数 sym.set_name 修改 player name,但其也不会对 selected 的值做检查,配合上信息泄露,可以导致任意地址写。

动态分析

漏洞大概清楚了,我们使用 gdb 动态调试一下,为了方便分析,先关闭 ASRL。gef 有个很强大的命令 heap-analysis-helper,可以追踪 malloc()free()realloc() 等函数的调用:

很好地验证了球员分配和删除的过程。

漏洞利用

alloc and select

然后是内存,根据我们对堆管理机制的理解,这里选择使用 small chunk(球员 name chunk):

free

然后:

我们知道,当一个 small chunk 被释放后,会被放到 unsorted bin 中,这是一个双向链表,它的 fd 指针指向了链表的头部,即地址 0x00007ffff7dd1b78。然后使用命令 vmmap 获得 libc 被加载的地址,用链表头部地址减掉它,得到偏移。当开启 ASLR 后,其地址会变,但偏移不变。同时,释放的 player 1 chunk 被加入到 fastbins 单链表中。

再次 free,将 player 2 释放,因为 player 1 也是被释放的状态,所以两个 chunk 会被合并(其实 player 是 fast chunk,不会被合并,真正合并的是 name chunk):

alloc again

添加一个球员,player chunk 将从 fastbins 链表中取出,而 name chunk 将从 unsorted_bin 中取出:

edit and get shell

编辑 selected 处的 chunck,即 name 3:

函数 atoi@got 已经被我们覆盖为 system@got,当调用 atoi 时,实际上是执行了 system('sh'):

到这里,我们可以重新启用 ASLR 了,该保护机制已经被绕过。

Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?