The program is a note editor running inside a x86 64-bit unicorn engine instance.
The start script is provided (see start.py)
Print the flag flag2.tzt, located in 0xFFFFFFFF81000000 + 0x5000
KERNEL_ADDRESS = 0xFFFFFFFF81000000
...
def start_userland():
...
mu.mem_write(KERNEL_ADDRESS + 0x5000, flag2)The start script adds a few hooks in start_kernel:
# Handle kernel code
mu.hook_add(UC_HOOK_CODE, handle_kernel, None, KERNEL_ADDRESS, KERNEL_ADDRESS+MAPPING_SIZE)
# Handle IN instruction
mu.hook_add(UC_HOOK_INSN, handle_kernel_in, None, 1, 0, UC_X86_INS_IN)
# Handle OUT instruction
mu.hook_add(UC_HOOK_INSN, handle_kernel_out, None, 1, 0, UC_X86_INS_OUT)
# Handle interrupts
mu.hook_add(UC_HOOK_INTR, handle_kernel_interrupt)
# Handle invalid memory access
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, handle_kernel_invalid)The handle_kernel_interrupt callback checks the interrupt number and rax. If rax == 0, mem_protect is called. It can be used to change permission to read the flag.
def handle_kernel_interrupt(uc, intno, data):
if intno == 0x70:
rax = uc.reg_read(UC_X86_REG_RAX)
if rax == 0:
rdi = uc.reg_read(UC_X86_REG_RDI)
rsi = uc.reg_read(UC_X86_REG_RSI)
rdx = uc.reg_read(UC_X86_REG_RDX)
# mem_protect(addr, size, perms)
uc.mem_protect(rdi, rsi, rdx)
elif rax == 7:
rdi = uc.reg_read(UC_X86_REG_RDI)
rsi = uc.reg_read(UC_X86_REG_RSI)
rdx = uc.reg_read(UC_X86_REG_RDX)
# mem_read(addr, size)
buf = str(eval(str(uc.mem_read(rdi, rdx))))
uc.mem_write(rsi, buf)
uc.reg_write(UC_X86_REG_RAX, len(buf))The custom kernel defines 3 system calls: read(0), write(1) and mprotect(10).
Since there is a mov eax, 0; int 0x70; iretd in mprotect, uc.mem_protect will be called in handle_kernel_interrupt
In userland, show_note has an indirect call using rax, and the note content is used for rdi, rsi, rdx, rcx and rdx (in order)
this can be used to do syscalls by putting the address of __syscall in rax. Note that the registers are swapped in __syscall:
There is a check in read_line that prevents us from inputting the mprotect syscall number 10. But we can use this same vulnerability to use do_read to write it instead.
There is a check in the custom kernel's write syscall that prevents us from printing the flag.
Once mprotect is called to enable writing to the kernel space, we can write a custom syscall based on this but without the check, which is simply:
mov rcx, rdx
mov rax, rcx
mov dx, 0x3f8
rep outsb
iret- Call
do_readwith address ofnotes[0]as*bufand 0x28 ascount. - Inside
do_read's input, write notes[0] to domprotectsyscall to permit writing kernel code. - Use the
readsyscall to write custom write syscall without the check inside kernel space. - Call the custom write syscall to print the flag
Is in expoit.py





