Writeup: FlareOn 2021: 007 - spel
1. TLDR
2. Input data
The challenge file is here. Hasło: flare.
The available file is spel.exe
3. Initial analysis
After launching, I saw an error message:
Is there something wrong with the application?
I thought there was something wrong with the setup of my environment. Maybe I had a problem with the sound? No access to some device? The wrong version of the system? While debugging the application, I found that the process continued to run after closing the window, hmmmm…..
In the disassembled code, I saw a lot of calls from the CWinApp class indicating that the application code was written using the Microsoft Foundation Class (MFC) library. Below is an example of the list of functions from this library used in the spel.exe application:
I looked through the list of imports for calls:
CreateThread
CreateProcessInternalW
VirtualAlloc
VirtualProtect
While doing so, I noticed an unusual (at least in my opinion) function VirutalAllocExNuma
An attempt to locate the code snippet calling this function (in the code graph view) failed:
Locating the VirutalAllocExNuma function in the listing view revealed an interesting piece of code. My attention was drawn to the long string of mov commands arranging data in memory, which was completed by arranging the “flare” letter, followed by memory allocation, its copying and execution of the data as code:
4. Detailed analysis
Using x64dbg, I executed the code to the breakpoint set to VirutalAllocExNuma, which allocated the address 0x260000, and after calling the memmove function, I saw a potential shellcode at this address. It was probably fine, the first command was call (E8):
Using Process Hacker, I dumped the entire memory area:
In order to limit the size of the dump to the part that had any relevance to the shellcode, I opened a memory dump in HxD. I quickly noticed that there was a PE file right next to the shellcode, which I extracted, and without analyzing the shellcode I assumed it was loaded by it.
The loaded file turned out to be a DLL file:
After loading the dll into the IDA tool, I noticed two interesting things:
- the DLL had no exports except DllEntryPoint.
- the DLL function used a very long array of bytes, which it passed to a function I didn’t know. A very long array that was another PE file.
So I extracted a nested file:
> emit spel-stage2.dll | carve-pe > spel-stage3.dll
Another loaded file turned out to be a DLL file:
Static analysis of the code led me to several observations:
- Functions are dynamically imported
- The names of imported functions and libraries are obfuscated
- All strings are obfuscated: encrypted with xor, the key of which was each time found several instructions after the ciphertext was loaded into memory. The figure below shows the loading of the ciphertext and decryption, respectively:
5. Deobfuscation
The names of libraries were mapped from integers, constructing an enumeration type made it easier to analyze such a method of code obfuscation:
The obfuscation of the loaded function names itself was not a problem, thanks to the use of HashDb and IDA environment plugin database
I solved the string obfuscation with the following script:
import idautils
import idc
import ida_ua
import ida_bytes
import idaapi
import struct
import malduck
def get_ciphertext(ea):
input = list()
for x in range(0,15):
if ida_ua.ua_mnem(ea) == 'mov' and idc.get_numbered_type_name(idc.get_operand_type(ea, 1))=='DWORD':
value = idc.get_operand_value(ea, 1)
if value!=0:
value = struct.pack("<I", value)
input.append(value)
else:
break
ea = ida_bytes.next_head(ea, ida_ida.cvar.inf.max_ea)
return (ea,bytes().join(input))
def get_key(ea):
while ida_ua.ua_mnem(ea) != 'xor' or idc.get_numbered_type_name(idc.get_operand_type(ea, 1))!='DWORD':
ea = ida_bytes.next_head(ea, ida_ida.cvar.inf.max_ea)
key = idc.get_operand_value(ea, 1)
key = struct.pack("<I", key)
return (ea,bytes(key))
ea = idc.here()
ea, ciphertext = get_ciphertext(ea)
ea, key = get_key(ea)
plaintext = malduck.xor(key, ciphertext)
plaintext_utf8 = plaintext.decode("UTF-8")
plaintext_utf16 = plaintext.decode("UTF-16")
print(f"key:{key}")
print(f"ciphertext:{ciphertext}")
print(f"plaintext_utf8:{plaintext_utf8}")
print(f"plaintext UTF-16:{plaintext_utf16}")
Further analysis of the program’s code made it possible to notice that the program used the bcrypt library and its provided implementation of the AES-128 algorithm. Thus, it loaded and decrypted a fragment of one of the resources with id 128:
6. Payload analysis
I recovered the contents of this resource with the CFF Explorer tool, the actual ciphertext is highlighted in red:
The key used was one of the strings:
IV was a string: 80808080808080808080808080808080
So I decrypted the ciphertext using Binary Refinery
$ emit 80808080808080808080808080808080D7FB7E628DAB8765CD7185CE530F5A8C2D8A4537124B791D40DA768626D3D372 | hex | aes --mode cbc -i cut::16 d41d8cd98f00b204e9800998ecf8427e
l3rlcps_7r_vb33eehskc3
After decryption, the string was blacked out again with xorem and the order of the characters was changed:
7. Reading the flag
It was enough to ignore the stream cipher and read the correct order of characters:
p = "l3rlcps_7r_vb33eehskc3"
indexes = [12,13,6,8,7,6,5,1,0,3,4,17,15,20,19,21,2,10,16,11,14,2]
t=[]
for i in indexes:
t.append(p[i])
t = ''.join(t)
print(t)
The flag:
b3s7_sp3llcheck3r_ev3r@flare-on.com