tl;dr
- Dynamically resolved hashed API
- Tls_call_back based anti-debug check
- AntiDebugFlag check implemented using ProcessInformationClass
- AES_CBC decryption of image to find flag
Challenge points: No. of solves: Solved by: retr0ds || AmunRha
Challenge Description#
You finally manage to remotely connect to a computer onboard the alien vessel to
shut down the defense mechanisms. However, it instantly starts acting up and ends up
deploying malware as a defense mechanism. All your documents including your hard
earned map of the vessel topology is now encrypted.
And we are given two files
- Challenge.exe
- vessel_map.jpeg.owo
So, on the base level, it ought to be malware that encrypts files Now we move on to figuring out how.
Triaging:#
Just running the binary inside a VM doesn’t seem to affect much inside the binary, nor is trying to pass it as an argument seem to make a difference.
And trying pintools on the binary also seems to result in an error. Hmm, that’s odd, seems to have implemented an anti-debug check
Static Analysis:#
It is UPX packed, so we go ahead and unpack it using
upx -d challenge.exe
Now, load it back again in IDA.
Functions:#
main
We find main() fairly easily, given it’s not stripped
sub_140001080 (AKA) GetFuncByHash
Main first calls these functions with an integer value return values are in v4 and v5. And later v4 and v5 are called dynamically as functions. So it is safe for us to assume that this would be a GetFunctionByHash
sub_1590
Unsure of what it does, we’ll keep it aside for now
v4 and v5 functions
The dynamically resolved functions are now called where unkn_array_1 is a byte_array of length 16. We hope to find this out later during the process of debugging to see what are the APIs being called
sub_140001210 (AKA) Unkn_func_3
This function also employs GetFuncByHash() and calls another API, it is safe to assume v5 might be the return value. This in turn is being used as an argument in the next function call based on the function parameter a1 (a1 passed as v8 in main, is the variable used inside the API called by v5 and v4)
We’ll get to how and why I named the byte array unkn_16_byte
in the next sub_function
sub_140001290 (AKA) Encrypt_Func
Now we get to the Function which seems to give us a basic idea of what exactly is being carried out in terms of encryption inside the binary.
We seem to check for this directory,
and a bunch of operations(analyzed below) later we see below a set routine of syscalls
OpenFile ReadFile CloseFileHandle
Some function with two bytearray’s as args and v17, v18(the buffers from readfile)
Then Writefile with v18,v17 as args CloseFileHandle
So it is safe to assume and form a HUNCH that,
- It takes in Two buffers B1 (unkn_16_byte) and B2 (unkn_array_1) each 16 bytes in length
- Reads from File A
- Encrypts file_bytes using B1, and B2 under some Encryption Algorithm and stores it into a new file.
- Both of these B1 and B2 are 16 bytes long and they
- Also going by the strcpy(), a hunch can be that it stores the filename and is appended the “.owo” before writing to it.
The current hunch is going to be that the encryption algorithm used is AES under CBC mode given it has a key as well as a
Dynamic Analysis#
Current Objective:
- Get the debugging process up and running
Attaching a debugger and stepping through results in an exception being thrown with error code 0x5 for MEMORY_ACCESS_VIOLATION inside ntdll.
Now, that’s interesting!! So, the malware author implemented either an existing or a custom anti-debug check method/routine wherein the process can identify if it is being debugged before even it gets to the challenge file’s code/the start()
So, the program is started by the OS, and during the RT setup, some function checks if there is a presence of a debugger or not.
Now we go back into the binary and search for “Debugger” inside functions we get nothing, next thought is to check for it in strings, and Lo and Behold! we are led to Tls_Callback_0
So googling about this we get to : Tls_Callback_0_Anti-Debug
TLS callback is a function that is called before the process entry point executes.
If you run the executable with a debugger, the TLS callback will be executed before
the debugger breaks. You can see the TLS callback is called by the loader during
process startup.
So right now we just patch the assembly from a jz to jnz to let debuggers work through this
And BOOM! now we can enter the user code of the binary.
Objective : Get the debugging process up and running
Current Objective: Figure out what APIs are being resolved by hashing
In main :
v4() <—–> NtAllocateVirtualMemory v5() <—–> NtWriteVirtualMemory
So it creates a virtual memory space and writes the contents in unkn_array_1 into that memory space
We set a Hardware Breakpoint
at this place just in case
In sub_1210 (AKA) Unkn_func_3 :
v3() <—-> NtQueryInformationProcess
Objective down: Figure out what APIs are being resolved by hashing
Function specific reversing:#
sub_1590#
Upon debugging we see this function achieves nothing nor does return anything useful under any register, it copies the buffer, runs a while loop for 0x1600 times, set the variable result to that value and returns result.
sub_140001210 (AKA) Unkn_func_3#
Stepping into this, we found out the API being called is NTQueryInformationProcess, and it returns v5.
So we pull up NtQueryInformationProcess’s MSDN
I have a suspicion that this might involve some form of tomfoolery with the PEB structure or ProcessInformationClass given a constant value has been passed on as the second argument
The next step was to google and confirm our hunch,
Search as such, gives us what we need right in the first link Anti-Debug-Checks
So the third argument (ie) v5 is dwProcessDebugFlags, and, if the process is being debugged it would be set to 0, and if not 1
With that in mind, we now move on to the next function call inside the binary,
Now this function is being called by using the first argument as the function pointer, and a1 happens to be the unkn_16_byte (which I assume is either the key or IV), and the second argument is the dwProcessDebugFlag
dwProcessDebugFlag is 0 if being debugged
is 1 if not being debugged
Now, we know the calling convention of windows functions
So we manually change the EDX register value to 1 and continue execution by hitting f7
now converting it all to code,
we see it makes use of xmmwords (128-bit registers) to do some operations
We are unsure of such opcodes, but our goal rn is to retrieve the unkn_16_byte array as if the program was never debugged.
And we seem to easily accomplish that by just passing through the instructions and finally examining the RCX value before the return.
We get the unkn_16_byte array to be
6D597133733676397924422645294840
sub_1290 (AKA) EncryptFunc#
This function seems to get the HANDLE to the first file under “C:\Users\Administrator\Documents\”
Then it goes onto this part of the codeThe above snippet is responsible for just copying over the file path string from the buffer to the variables. Unimportant.
This above part of the code is responsible for converting the filename to UTF-8 from UTF-16(Microsoft/Windows stores text in the form of UTF-16 to ensure broader encompassing of symbols/characters, this is AKA wide char where each character is given a set space of 2 bytes,in the event only a single byte is used, the unused byte is nulled out)
Then the UTF-8 converted text is put in the routine of
OPEN
READ
CLOSE
<ENCRYPT>
OPEN
WRITE
CLOSE
The Core#
And now we get to the core of the binary This encrypt function determines the entire functionality purpose of the binary
Before going into the function, we see it takes in the unkn_16_byte we retrieved and another array with just the null bytes(chances for it being the IV incase this is AES)
Now we step into the function, by hitting f7 we get bytes, we continue hitting c to convert it to code and p to define it as a function in IDA
Once we are a couple of functions in, we notice
Checking out the byte array,
We see it is exactly the S-box used in AES.
Now we have confirmed the encryption algorithm being used as well by taking a step-by-step approach.
All we have left is to decrypt the given .owo file and that should give us the FLAG
The Script#
from Crypto.Cipher import AES
f1 = open("flag.png","wb")
x = AES.new((key=b'mYq3s6v9y$B&E)H@', mode=AES.MODE_CBC, iv=b'\x00'*16)
f2 = open("vessel_map.jpeg.owo", "rb")
bytes = f2.read()
f1.write(x.decrypt(bytes))
key = b'mYq3s6v9y$B&E)H@'
is our unkn_16_byte in bytes format
iv = 16 null bytes (ie) ‘\x00’ * 16
OR
We can use CyberChef
To retrieve the flag file
The Flag#
HTB{573pp1ng_1n70_und0cum3n73d_4113n_4p15}