Escaping the matrix:�exploiting custom QEMU cpu bugs �from a HXP CTF 2024 task
By Disconnect3d @ AlligatorCon EU 2025
1
# whoami
2
Blog: disconnect3d.pl
justCattheFish CTF team captain
Staff Security Engineer @
Maintainer of Pwndbg/Pwndbg
HXP CTF 2024
3
4
The challenge TL;DR
5
Linux box
Linux VM
with custom CPU�run via QEMU TCG
Unprivileged shell
$
The challenge TL;DR
6
Linux box
Linux VM
with custom CPU�run via QEMU TCG
Unprivileged shell
$
flag.txt file
7
8
9
10
11
12
13
14
Kernel changes
QEMU changes
QEMU changes
19
QEMU changes
20
QEMU changes
21
QEMU changes
22
QEMU changes
23
QEMU changes
24
QEMU changes
25
QEMU changes
26
QEMU changes
27
QEMU changes
28
QEMU changes
29
Virtual to physical�address translation
31
32
64-bit �virtual address
33
CR3 register
points to the level 5 page table
34
CR3 register
points to the level 5 page table
35
CR3 register
points to the level 5 page table
36
CR3 register
points to the level 5 page table
37
CR3 register
points to the level 5 page table
38
CR3 register
points to the level 5 page table
39
CR3 register
points to the level 5 page table
40
CR3 register
points to the level 5 page table
41
CR3 register
points to the level 5 page table
QEMU changes
42
QEMU changes
43
TLB: Transation Lookaside Buffer
44
QEMU changes
45
QEMU changes
46
QEMU changes
47
QEMU changes
48
RWX perms? :)
QEMU changes
49
man msr
50
man msr
* requires "msr" kernel module to be loaded
51
man msr
* requires "msr" kernel module to be loaded
52
MSRs
53
MSR = Model Specific Register
Registers to configure X86/X64 CPU/OS specific features such as:
54
MSR = Model Specific Register
Registers to configure X86/X64 CPU/OS specific features such as:
55
open, read, write, close,�mmap, mprotect, munmap,�execve, fork, kill, exit, …
QEMU changes
56
QEMU changes
57
QEMU changes
58
How do we exploit it all?
Let's recall state we are in�&�features we have
State & features
61
State & features
62
State & features
63
State & features
64
State & features
65
How to exploit?
66
How to exploit?
Could we make busybox run our code?
What if it translated its own virtual memory into "our memory"???
67
68
static void gen_fscr(DisasContext *s) {
TCGLabel *l1 = gen_new_label(),
TCGLabel *l2 = gen_new_label();
const size_t slice_size_offset = offsetof(CPUX86State, scratch_config.slice_size);
const size_t slice_count_offset = offsetof(CPUX86State, scratch_config.num_active_slices);
const size_t va_base_offset = offsetof(CPUX86State, scratch_config.va_base);
const size_t access_offset = offsetof(CPUX86State, scratch_config.access_enabled);
tcg_gen_st_tl(tcg_constant_i64(1), tcg_env, access_offset);
// Calculate size
tcg_gen_ld32u_tl(s->tmp0, tcg_env, slice_size_offset);
tcg_gen_ld32u_tl(s->tmp4, tcg_env, slice_count_offset);
tcg_gen_mul_tl(s->tmp0, s->tmp0, s->tmp4);
// For loop to clear memory
gen_set_label(l1);
gen_update_cc_op(s);
TCGv tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_EQ, tmp, 0, l2);
tcg_gen_sub_tl(s->tmp0, s->tmp0, tcg_constant_i64(1));
tcg_gen_ld_tl(s->A0, tcg_env, va_base_offset);
gen_lea_v_seg(s, s->A0, R_ES, -1);
tcg_gen_add_tl(s->A0, s->A0, s->tmp0);
gen_op_st_v(s, MO_8, tcg_constant_i64(0), s->A0);
tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_NE, tmp, 0, l1);
gen_set_label(l2);
tcg_gen_st_tl(tcg_constant_i64(0), tcg_env, access_offset);
}
69
static void gen_fscr(DisasContext *s) {
TCGLabel *l1 = gen_new_label(),
TCGLabel *l2 = gen_new_label();
const size_t slice_size_offset = offsetof(CPUX86State, scratch_config.slice_size);
const size_t slice_count_offset = offsetof(CPUX86State, scratch_config.num_active_slices);
const size_t va_base_offset = offsetof(CPUX86State, scratch_config.va_base);
const size_t access_offset = offsetof(CPUX86State, scratch_config.access_enabled);
tcg_gen_st_tl(tcg_constant_i64(1), tcg_env, access_offset);
// Calculate size
tcg_gen_ld32u_tl(s->tmp0, tcg_env, slice_size_offset);
tcg_gen_ld32u_tl(s->tmp4, tcg_env, slice_count_offset);
tcg_gen_mul_tl(s->tmp0, s->tmp0, s->tmp4);
// For loop to clear memory
gen_set_label(l1);
gen_update_cc_op(s);
TCGv tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_EQ, tmp, 0, l2);
tcg_gen_sub_tl(s->tmp0, s->tmp0, tcg_constant_i64(1));
tcg_gen_ld_tl(s->A0, tcg_env, va_base_offset);
gen_lea_v_seg(s, s->A0, R_ES, -1);
tcg_gen_add_tl(s->A0, s->A0, s->tmp0);
gen_op_st_v(s, MO_8, tcg_constant_i64(0), s->A0);
tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_NE, tmp, 0, l1);
gen_set_label(l2);
tcg_gen_st_tl(tcg_constant_i64(0), tcg_env, access_offset);
}
70
static void gen_fscr(DisasContext *s) {
TCGLabel *l1 = gen_new_label(),
TCGLabel *l2 = gen_new_label();
const size_t slice_size_offset = offsetof(CPUX86State, scratch_config.slice_size);
const size_t slice_count_offset = offsetof(CPUX86State, scratch_config.num_active_slices);
const size_t va_base_offset = offsetof(CPUX86State, scratch_config.va_base);
const size_t access_offset = offsetof(CPUX86State, scratch_config.access_enabled);
tcg_gen_st_tl(tcg_constant_i64(1), tcg_env, access_offset);
// Calculate size
tcg_gen_ld32u_tl(s->tmp0, tcg_env, slice_size_offset);
tcg_gen_ld32u_tl(s->tmp4, tcg_env, slice_count_offset);
tcg_gen_mul_tl(s->tmp0, s->tmp0, s->tmp4);
// For loop to clear memory
gen_set_label(l1);
gen_update_cc_op(s);
TCGv tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_EQ, tmp, 0, l2);
tcg_gen_sub_tl(s->tmp0, s->tmp0, tcg_constant_i64(1));
tcg_gen_ld_tl(s->A0, tcg_env, va_base_offset);
gen_lea_v_seg(s, s->A0, R_ES, -1);
tcg_gen_add_tl(s->A0, s->A0, s->tmp0);
gen_op_st_v(s, MO_8, tcg_constant_i64(0), s->A0);
tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_NE, tmp, 0, l1);
gen_set_label(l2);
tcg_gen_st_tl(tcg_constant_i64(0), tcg_env, access_offset);
}
71
static void gen_fscr(DisasContext *s) {
TCGLabel *l1 = gen_new_label(),
TCGLabel *l2 = gen_new_label();
const size_t slice_size_offset = offsetof(CPUX86State, scratch_config.slice_size);
const size_t slice_count_offset = offsetof(CPUX86State, scratch_config.num_active_slices);
const size_t va_base_offset = offsetof(CPUX86State, scratch_config.va_base);
const size_t access_offset = offsetof(CPUX86State, scratch_config.access_enabled);
tcg_gen_st_tl(tcg_constant_i64(1), tcg_env, access_offset);
// Calculate size
tcg_gen_ld32u_tl(s->tmp0, tcg_env, slice_size_offset);
tcg_gen_ld32u_tl(s->tmp4, tcg_env, slice_count_offset);
tcg_gen_mul_tl(s->tmp0, s->tmp0, s->tmp4);
// For loop to clear memory
gen_set_label(l1);
gen_update_cc_op(s);
TCGv tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_EQ, tmp, 0, l2);
tcg_gen_sub_tl(s->tmp0, s->tmp0, tcg_constant_i64(1));
tcg_gen_ld_tl(s->A0, tcg_env, va_base_offset);
gen_lea_v_seg(s, s->A0, R_ES, -1);
tcg_gen_add_tl(s->A0, s->A0, s->tmp0);
gen_op_st_v(s, MO_8, tcg_constant_i64(0), s->A0);
tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_NE, tmp, 0, l1);
gen_set_label(l2);
tcg_gen_st_tl(tcg_constant_i64(0), tcg_env, access_offset);
}
72
static void gen_fscr(DisasContext *s) {
TCGLabel *l1 = gen_new_label(),
TCGLabel *l2 = gen_new_label();
const size_t slice_size_offset = offsetof(CPUX86State, scratch_config.slice_size);
const size_t slice_count_offset = offsetof(CPUX86State, scratch_config.num_active_slices);
const size_t va_base_offset = offsetof(CPUX86State, scratch_config.va_base);
const size_t access_offset = offsetof(CPUX86State, scratch_config.access_enabled);
tcg_gen_st_tl(tcg_constant_i64(1), tcg_env, access_offset);
// Calculate size
tcg_gen_ld32u_tl(s->tmp0, tcg_env, slice_size_offset);
tcg_gen_ld32u_tl(s->tmp4, tcg_env, slice_count_offset);
tcg_gen_mul_tl(s->tmp0, s->tmp0, s->tmp4);
// For loop to clear memory
gen_set_label(l1);
gen_update_cc_op(s);
TCGv tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_EQ, tmp, 0, l2);
tcg_gen_sub_tl(s->tmp0, s->tmp0, tcg_constant_i64(1));
tcg_gen_ld_tl(s->A0, tcg_env, va_base_offset);
gen_lea_v_seg(s, s->A0, R_ES, -1);
tcg_gen_add_tl(s->A0, s->A0, s->tmp0);
gen_op_st_v(s, MO_8, tcg_constant_i64(0), s->A0);
tmp = gen_ext_tl(NULL, s->tmp0, s->aflag, false);
tcg_gen_brcondi_tl(TCG_COND_NE, tmp, 0, l1);
gen_set_label(l2);
tcg_gen_st_tl(tcg_constant_i64(0), tcg_env, access_offset);
}
But… what happens if an instructions stops in the middle?
Exploit idea
Exploit idea
74
Exploit idea
75
And that's it…
any simple questions? :)
By Disconnect3d
76