In this challenge we're given an x64 ELF binary. The program acts as a userspace host for KVM virtualization. Among other things, it sets up the VM's address space, initializes the necessary VM registers, copies the code from the ".payload" section to it, then finally runs it.
Additionally, the userspace host expects the VM to trap when executing the three illegal instructions : IN, OUT, and HLT as shown below. The host will do some processing and then fix the VM's state so it can graciously continue executing.
And here is an instance of a HLT instruction within the VM's code.
Let's now describe the behavior of the host for each illegal instruction.
IN (port 0xE9) : Reads a single character from STDIN and returns it to the VM (The first thing that the VM does is read user input from STDIN).
OUT (port 0xE9) : Outputs a single character to STDOUT.
HLT : Before the VM executes a HLT instruction, it moves a special value into EAX. After it traps, our host reads this value and uses it as a key in an array. Each key corresponds to a handler routine within the VM's address space.
Here is a list of all present handlers :
What the host does then is set VM's RIP register to the corresponding handler.
And by examining the handlers, we see that they invoke each other using the HLT instruction.
Now, let's try to examine what the VM does and figure out what these handlers are used for.
Briefly, 0x2800 bytes are read from STDIN and for each of these bytes sub_1E0 is called. The first time it's called, this function takes the user-supplied character and the address 0x1300 which points to some data.
sub_1E0 initializes local variables and then branches to the handler at
- Extract the correct bit array from 0x580 as bytes.
- Reverse the order of the bytes and then convert them to binary representation. We reverse the order because we want to traverse the tree from root to leaf, doing the opposite would be impossible since all bits are concatenated. Also, when doing this, we'll start by extracting the last character and so on until we reach the first.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from idc import * | |
root = 0x1300 | |
flag = '' | |
def tobits(s): | |
result = [] | |
for c in s: | |
bits = bin(ord(c))[2:] | |
bits = '00000000'[len(bits):] + bits | |
result.extend([[int(b) for b in bits]]) | |
return result | |
def traverse_to_leaf(element) : | |
global bits | |
if Byte(element) == 0xFF : | |
bit = bits.pop(0) | |
if element == root and bit == 1 : | |
#skip null byte | |
return | |
if bit == 0 : | |
#left | |
traverse_to_leaf(Qword(element + 8)) | |
else : | |
#right | |
traverse_to_leaf(Qword(element + 16)) | |
else : | |
global flag | |
flag += chr(Byte(element)) | |
bl = tobits(GetManyBytes(0x580, 0x54A)) | |
bl.reverse() #Reverse so we can start exploring from the root | |
#Flatten the list | |
bits = [] | |
for byte in bl : | |
for bit in byte : | |
bits.append(bit) | |
while bits : | |
traverse_to_leaf(root) | |
print flag[::-1] #reverse |
As a result, we get the flag and we see that the VM was expecting a tar file as input:
flag.txt0000664000175000017500000000007113346340766011602 0ustar toshitoshiflag{who would win? 100 ctf teams or 1 obfuscat3d boi?}
Thank you for reading :)
You can follow me on Twitter : here