This is the twelfth and final part of the Flare-On 6 CTF WriteUp Series.
12 – help
The challenge reads
You’re my only hope FLARE-On player! One of our developers was hacked and we’re not sure what they took. We managed to set up a packet capture on the network once we found out but they were definitely already on the system. I think whatever they installed must be buggy – it looks like they crashed our developer box. We saved off the dump file but I can’t make heads or tails of it – PLEASE HELP!!!!!!
We have two files –
help.dmp – A 2 GB memory dumphelp.pcapng – Packet capture
Identifying the image
For analyzing the memory dump we will be using Volatility along with WinDbg. Make sure to use the bleeding edge version of Volatility on GitHub and not the 2.6 release which is quite old.
At first we need to identify the image with imageinfo or kdbgscan command. Unfortunately, for this specific image none of the commands work out of the box as shown in the snippet below.
$ vol.py -f help.dmp imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search…
WARNING : volatility.debug : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug : Alignment of WindowsCrashDumpSpace64 is too small, plugins will be extremely slow
WARNING : volatility.debug …
In such cases where Volatility is unable to infer on its own, we have to manually specify the image profile to use with the –profile flag. For example using the profile Win7SP1x64 volatility correctly identifies the image.
$ vol.py -f help.dmp –profile=Win7SP1x64 imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search…
Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
AS Layer2 : WindowsCrashDumpSpace64 (Unnamed AS)
AS Layer3 : FileAddressSpace (/home…
Preliminary Analysis
Volatility supports a whole slew of different analysis commands. For brevity, we will only be mentioning the relevant commands but the reader is encouraged to try them all. For each of the commands we need to specify the profile using the –profile=Win7SP1x64 flag.
Screenshot command
This command takes a screenshot from each desktop on the system. Running the command generates several screenshots one of which looks interesting.
Figure 1: An interesting screenshot
As shown in Figure 1, there is Google Chrome and KeyPass running. A database named keys.kdb is currently open in the KeyPass instance.
Modules command
This command displays the list of kernel modules that were loaded in the system at the time the memory dump was captured.
$ vol.py -f help.dmp –profile=Win7SP1x64 modules
Offset(V) Name Base Size File
—————— ——————– —————— —————— —-
0xfffffa800183e890 ntoskrnl.exe 0xfffff80002a49000 0x5e7000 \SystemRoot\system32\ntoskrnl.exe
0xfffffa800183e7a0 hal.dll 0xfffff80002a00000 0x49000 \SystemRoot\system32\hal.dll
— snip–
0xfffffa800428ff30 man.sys 0xfffff880033bc000 0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys
There’s a driver named man.sys which was loaded from the path C:\Users\FLARE ON 2019\Desktop\man.sys. Since system drivers do not usually reside on the Desktop we can be pretty sure that this is related to the challenge.
Dumping man.sys
man.sys is loaded at the address 0xfffff880033bc000. We can either use Volatility or WinDbg for dumping the module. Here I have used WinDbg as I found it to work better than Volatility as far as memory dumping is concerned. Ensure that the path to the symbol server is properly set in WinDbg.
Figure 2: Missing MZ header
We can use the db command to hex dump a memory region. However as shown in Figure 2, the MZ header is missing from man.sys which indicate that the corresponding page must have been paged out from memory. Regardless, we can still use the .writemem command to dump a considerable chunk of memory (say 100KB) and load it as a binary file in IDA.
Figure 3: Dumping man.sys
The dumped file is of size 60 KB. Searching for strings we locate the path to the PDB file embedded in the PE.
Figure 4: Embedded PDB file path
Dumping other drivers and DLLs
Grepping for the strings “flareon_2019” and “pdb” we can find other relevant files that are related to the challenge as shown in Figure 5.
Figure 5: DLLs and drivers related to the challenge
List of drivers
stmeditshellcodedriverman
List of DLLs
cdcryptodllfiledllkeylogdllnetworkdllscreenshotdll
Among the drivers we already have man.sys. The other two – shellcodedriver and stmedit can be located in memory using the yarascan volatility command as shown in Figure 6.
Figure 6: Using yarascan
stmedit is located within memory of process svchost.exe with pid 876. Using the !process windbg extension, svchost.exe with pid 876 has its EPROCESS at fffffa80034a4b30
Figure 7: Finding EPROCESS of svchost
Knowing the address of EPROCESS we can set the process context using the .process command.
Figure 8: Setting the process context
Navigating to b260c9 we can cross check that it indeed contains the stmedit string as found in Volatility using yarascan.
Figure 9: Cross checking the output of yarascan
From here we can search backwards to locate the start of the MZ header as shown in Figure 10.
Figure 10: Searching backwards for MZ header
Next, we can use the .writemem command to dump stmedit.sys in the same way as we did earlier.
Figure 11: Dumping stmedit
Apart from shellcodedriver (it has the MZ header missing), the same technique can be reused to dump each of the 6 DLLs. (The complete shellcodedriver file will later be found in the pcap).
Analyzing the DLLs & drivers
Before we look at the DLLs separately there are several techniques that are common across all the DLLs and drivers.
None of the DLLs import WinAPI functions statically using the IAT. Instead functions are resolved dynamically using LoadLibrary or by parsing PEB_LDR_DATA. For obtaining the addresses of the API, either GetProcessAddress is used or in some cases the export table of the module is parsed. Further to harden analysis, the names of the functions are encrypted and are only decrypted at run-time before it’s about to be called.
For example in Figure 12, the rc4 function decrypts the stringCreateFileA. It is worth noting that each such string is encrypted with a different key.
Figure 12: Decrypting API names at runtime
The rc4 function takes four parameters –
A pointer to the keySize of the key in bytesA pointer to the encrypted buffer. After the function returns this will hold the decrypted contentsSize of the encrypted buffer
In Figure 12, the key and encrypted buffer are {0x91, 0xe8, 0xa5, 0x7d} and {0xbd, 0x64, 0x20, 0x46, 0xad, 0xad, 0xe8, 0x7a, 0x39, 0x7c, 0x26} respectively. This can be decrypted using the PyCryptodome Python library.
>>> from Crypto.Cipher import ARC4
>>> key = ”.join(map(chr, [0x91, 0xe8, 0xa5, 0x7d]))
>>> ct=””.join(map(chr, [0xbd, 0x64, 0x20, 0x46, 0xad, 0xad, 0xe8, 0x7a, 0x39, 0x7c, 0x26]))
>>>
>>> ARC4.new(key*2).decrypt(ct)
‘CreateFileA’
Lastly another technique common across all the DLLs is the use of a function dispatcher to make the WinAPI function call. The dispatcher takes a variable number of arguments depending on the WinAPI function it wants to call. Let’s look at two examples to make it clear.
Figure 13: Calling socket
In Figure 13, the method call_function is the function dispatcher we are talking about. Here it wants to call socket which is exported from Ws2_32.dll. The first parameter is a handle to the module containing the function; the second parameter is a pointer to a buffer containing the name of the function. The name of the function is decrypted at runtime as we saw just a while ago. The third parameter indicates the number of arguments that the function requires which is 3 as socket takes three arguments. After the third comes the actual arguments to the function.
Figure 14: Calling htons
Let’s consider another example as in Figure 14. The htons function exported from Ws2_32.dll takes a single parameter. Correspondingly the third parameter passed to call_function is 1. After that we have the actual argument to htons – the binding port number.
cd.dll
Exports a function named cSets up a listener on port 4444 and spawns a thread for each incoming connectionReads 4 bytes from the socket. This indicates the size of the payload about to followThe next 4 bytes are some sort of code (Figure 15) based on which it sends a IOCTL to a driver.Figure 15: Various IOCTL codesThe driver to which it sends the IOCTLs is named FLID (Figure 16).Figure 16: Driver has the name FLID
cryptodll
Exports a function named eThis function takes in a single parameter – a pointer to a structure of  the following formstruct Buffer_info
{
QWORD src_buffer_size;
LPBYTE src_buffer;
QWORD dst_buffer_size;
LPBYTE dst_buffer;
}The function compresses (LZNT1) and encrypts (RC4) src_buffer to dst_bufferFor Compression it uses the NT function RtlCompressBufferThe key used for encryption is the current username obtained from GetUserNameA
filedll
Exports a function named iContains functionality to  create, read, write and search for a file as shown in Figure 17.Figure 17: Functionality in filedll
keylogdll
Exports a function named lAs its name suggests, the dll implements key logging functionality
networkdll
Exports a function named sContains functionality to send data to the host 192.168.1.243 at a configurable port as shown in Figure 18.Figure 18: Networkdll can send a piece of data to 192.168.1.243
screenshotdll
Exports a function named tContains functionality to capture a bitmap screenshot of the desktop
shellcodedriver
This is a 32-bit driver whose sole purpose is to execute a piece of shellcode in kernel space
stmedit
Figure 19: Hardcoded XOR key
man
This driver handles IOCTLs from cd.dll as in Figure 20.Compete understanding of this driver is not necessary to complete the challengeFigure 20: man.sys handles IOCTLs from cd.dll
Decrypting the pcap traffic
Figure 21: NetworkMiner
NetworkMiner is a great tool to get a quick summary of a packet capture. Using the tool we can see there are too many hosts involved. However not all of the traffic in the pcap is relevant to this challenge. By analyzing networkdll we already know that traffic to host 192.168.1.243 is related to this challenge. From cd.dll we also know any traffic to port 4444 is also relevant. All in all the following TCP streams in the pcap are important.
Traffic to host 192.168.1.243 on ports 6666, 7777, 8888Traffic to host 192.168.1.244 on port 4444
To extract the TCP streams from the pcap we can use tcpflow which automatically groups them by host and port. We get 285 flows in total out of which we only need to consider the relevant traffic as just discussed.
Decrypting traffic to 192.168.1.244:4444
This traffic is just XOR encryted with the 8 byte key (5d f3 4a 48 48 48 dd 23) which we found earlier. There are 20 such TCP streams. After decrypting, one stream by virtue of its large size (4 KiB) stands out. This stream contains the