Introduction
As the days for the final round of the game, draw near, rumors are beginning to spread that one faction in particular has rigged the final! In the meeting with your team, you discuss that if the game is indeed rigged, then there can be no victory here... Suddenly, one team player barged in carrying a Windows Laptop and said they found it in some back room in the game Architects' building just after the faction had left! As soon as you open it up it turns off due to a low battery! You explain to the rest of your team that the Legionnaires despise anything unethical and if you expose them and go to them with your evidence in hand, then you surely end up being their favorite up-and-coming faction. "Are you Ready To Run with the Wolves?!"
Type: Forensics
Difficulty: Insane
Event: Hack The Box Cyber Apocalypse 2024: Hacker Royale (ctftime)
Initial recon
Given: Windows hibernation file.
$ hiberfil.sys (read-only)
f9f96147bfe041e3174eb988e4fc10e16c17a82b20bb6b7f9aff23126b523699
Investigation
Windows Hibernation File
hiberfil.sys
is a hidden system file is located in the root folder of the drive where the operating system is installed. The Windows Kernel Power Manager reserves this file when you install Windows. The size of this file is approximately equal to how much random access memory (RAM) is installed on the computer. The computer uses the hiberfil.sys
file to store a copy of the system memory on the hard disk when the hybrid sleep setting is turned on. If this file is not present, the computer cannot hibernate.Learned by experience, whenever I'm dealing with the memory dumps I start with The Volatility Framework. Unfortunately my hopes were quickly vanished becasue volatility
can't operate directly on the hiberfil.sys
. I scratched my head becasue it seemed unlikely that noone even thought about adding support for the hibernation files to the such profound tool.
Luckily for all of us, as of the time this CTF event started, ForensicXlab already did the work and created a pull request for volatility3
adding two new plugins: windows.hibernation.Info
and windows.hibernation.Dump
.
Seeing how long it took someone to finally come up with the plugins that easen working with hibernation memory dumps - π for the k1nd0ne, arigato!
After switching to the PR branch, my volatility
was ready to create the memory_layer.raw
file so I can finaly start analysis.
For the sake of simplicity I'm using
vol3
alias forpython c:/ws/tools/volatility3/vol.py
$ git fetch origin pull/1036/head:feature/hibernation-layer
$ vol3 -f hiberfil.sys windows.hibernation.Dump --version 0
Why am I using version
0
? I just assumed that memory dump originates from Windows 10, and wanted to start from the latest possible
0 [Windows 10 1703 to Windows 11 23H2]
1 [Windows 8/8.1]
2 [Windows 10 1507 to 1511]
3 [Windows 10 1607]
$ memory_layer.raw (read-only)
0cfb69681ee202f8f65245301ec113f6ea5fb1546f8bac0279baf65d8a800bc0
Analyzing memory dump
Now it's the most interesting and time-taking phase. I dump and organize various outputs from volatility
simultanously trying to note down anything that could be analized later in the greater details. This was the first time when I was solving the Insane difficulty challenge, so my choice of plugins expanded to the ones I've never tried - becasue I really couldn't pinpoint what else could I do with what I have at my disposal. But about this a bit later.
I have used following plugins (some of them targeted on the specific processes):
- windows.pslist.out
- windows.dumpfiles.DumpFiles
- windows.cmdline.CmdLine.out
- windows.envars.Envars.6348.out
- windows.filescan.FileScan.out
- windows.mftscan.MFTScan.out
- windows.mbrscan.MBRScan.out
- windows.netscan.NetScan.out
- windows.netstat.NetStat.out
- windows.dlllist.DllList.out
- windows.handles.Handles.6348.out
- windows.handles.Handles.out
- windows.modscan.ModScan.out
- windows.modules.Modules.out
- windows.privileges.Privs.6348.out
- windows.registry.hivelist.HiveList.out
- windows.registry.hivelist.HiveScan.out
- windows.vadinfo.VadInfo.out
- windows.strings.Strings
Top ones are what usually was enough for me to find the malicious process and dump it so I can analyze further, discover shell run command or suspicious environment variable. In the middle are ones that I run depending on the context finshing with these I've never touched. windows.strings.Strings
is a really "I don't know what I am doing, just grab me all strings from that file and I will figure it out later when you finish, in like... 4 or 5 hours from now".
Here is my collection
As you can probably see one of the process cought my attention and will forever have special place in my heart π .
Process #6348
PID | PPID | Offset(V) | ImageFileName |
6560 | 5852 | 0xac8d5fba2080 | explorer.exe |
6348 | 6560 | 0xac8d5ef4e080 | TheGame.exe |
2460 | 6348 | 0xac8d5f8e2080 | conhost.exe |
TheGame.exe
is not usual executable you find on Windows system. Especialy one residing in this location (thanks to windows.cmdline.CmdLine
):
6348 TheGame.exe "C:\Users\architect\Desktop\publish\TheGame.exe"
Same executable appear in the list of connections
Offset Proto LocalAddr LocalPort ForeignAddr ForeignPort State PID Owner Created
0xac8d5f6dd8a0 TCPv4 10.0.2.15 49829 20.12.23.50 443 CLOSED 6348 TheGame.exe 2024-02-08 22:43:14.000000
..but again, cul-de-sac. It is easy to establish that 20.12.23.50
belongs to Windows Update service. Chasing answer if it is true, is not my concern especially because connection was established over SSL - and that's definitely not a common thing to put into CTF challenge.
Anyway, I continue with dumping the binary from the process.
$ vol3 -f memory_layer.raw windows.pslist.PsList --pid 6348 --dump
Hmm, wrong file?
Maybe the binary is incomplete/malformed. Let's see if there are other places from which we can get the binary.
After checking all files it becomes clear that we won't get anything apart from one file. I proceed with decompiling TheGame.dll
in ILSpy.
Let's have a look at the Main(..)
method.
Try loading list of DLLs.
Initilize AMSI engine.
Tests the engine using hardcoded binary data.
Load
kernel32.dll
test it with AMSI.If AMSI does not report file as malicious, tries injecting DLL with the same binary data that raises AMSI alert.
Goes limbo with many
Nop
commands.
Ok, a lot is happening there so let's start with some definitions.
Antimalware Scan Interface (AMSI)
No-operation instruction (nop, no-op)
Although I suspect what that base64 encoded binary data is, let's throw it into CyberChef.
EICAR Anti-Virus Test File
Okey, let's think for a moment.
In the main(..)
method of a TheGame.dll
.NET library there are 8 more DLLs loaded, do nothing with them, then proceed to load some AMSI module with a purpose of.. writing some bytes to the core Windows library.. when for sure it is already in use! So that won't gonna happen - that's why the exception is swollen in try/catch
clause.
Ok now I will start talking like it is just an another CTF challenge, another Mr. Know-it-all. But reaching from this point to the moment when I realized what I have to do, took me 4 (four) days. To be more precise something around 20-25hrs but because of what is going to happen is probably why this challenge is rated as Insane.
It's either a rabbit hole that someone put some effort to setup, or there is something that I am missing from the picture.
You explain to the rest of your team that the Legionnaires despise anything unethical and if you expose them and go to them with your evidence in hand, then you surely end up being their favorite up-and-coming faction. "Are you Ready To Run with the Wolves?!"
Why are the Ready To Run
and Wolves
capitalized?
..i don't think this is what the author wants me to search for... but for sure I can recommend this:
Oh you sneaky s..
Ready to run with the wolves
I'm in no competence to explain what ReadyToRun Deployment, Ahead-Of-Time Compilation are and how important they are not only for this challenge but for all dotnet
developers and security researchers. That's why I am recommending to read great articles and resources I've gathered in the last section as well as other write-ups for this challenge. And for now let me explain this concept in the Minimum-Valuable-Product way.
R2R and AOT
Step back. Source Code -> IL -> Native
. CIL programming language code is translated to the Common Intermidiate Language which then Common Language Runtime executes by performing Just-in-time Compilation, compiling CIL to native instructions. This happens this way to deliver versatility for CIL languages - software can run on different platforms, not only on that on which source code was compiled (because each platform has its CLR).
(source: medium.com/@kunaltandon.kt/c-clr-il-jit-com..)
Ahead-Of-Time was a term existing for some time now, but R2R was introduced with the release of .NET Core 3.0. If you are familiar with the term Template Specialization (or similar) - you are golden. At least that's how I understand whats happening.
So basicaly, you have your source code translated to CIL, generic code. But instead of waiting for your software to run on specific platform and let CLR to compile your CIL into native machine instructions - you just compile it ahead of time. Then you include that compiled, native instructions into your software so it is ready to run on that platform.
R2R Stomping
Ok, so how does it relate to our TheGame.dll
? See, when decompiler inspects library or executable it just by default run through the IL and extrapolates on what the software is doing. But when such software runs, it executed the native instructions - provided both from CLR and AOT Compilation.
What if.. someone compiles the DLL with R2R targeted specific architecture - then for example injects own native instructions overridding the AOT compilation. Assuming there is just enough space in R2R portion of assembly to hold new payload, that one will end up having a single software copy that can behave very differently from how it was coded!
I encourage at least glancing throught the R2R Stomping article for more detailed presentation.
This, my ladies and gents - is the reason I was loosing my mind over this one challenge for a couple last days.
Detecting R2R
Actually there are many signs that we are dealing with R2R assembly - I just didn't know about them π
dotPeek:
dnSpy:
R2R File Format specifies that (quote from the linked GitHub document):
The PE file is always platform specific
CLI Header Flags field has set
COMIMAGE_FLAGS_IL_LIBRARY
(0x00000004) bit setCLI Header
ManagedNativeHeader
points to READYTORUN_HEADER
And finally, if you are not a fan of manual/visual analysis - here comes the quickest way to check if you are dealing with the R2R assembly - rule for the ultimate Yara.
import "pe"
// source:
// https://research.checkpoint.com/2023/r2r-stomping-are-you-ready-to-run/
rule r2r_assembly
{
meta:
author = "jiriv"
description = "Detects dotnet binary compiled as ReadyToRun - form of ahead-of-time (AOT) compilation"
condition:
// check if valid PE
uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and
// check if dotnet -> .NET Directory is present
pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].virtual_address != 0 and
// check if ManagedNativeHeader exists -> ManagedNativeHeader RVA is not 0 inside .NET Directory
uint32(pe.rva_to_offset(pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].virtual_address) + 0x40) != 0 and
// check if it is R2R -> RTR magic signature is present (0x00525452 == "RTR" in ascii)
uint32(pe.rva_to_offset(uint32(pe.rva_to_offset(pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].virtual_address) + 0x40))) == 0x00525452
}
Extracting shellcode
Shellcode
Now what we know what TheGame.dll
really is, lets see the Main(..)
method once again, but from different perspective. Load the assembly into ILSpy, right click on the Main(..)
and choose "Decompile to new tab". We can stack two of these horizontally so we can have easier time to see the difference.
In one of the tabs select "ReadyToRun" (to be honest, I've never been aware that this dropdown exists..).
I'm not a assembly guy, but what can be seen in ReadyToRun view, is nothing about loading some DLLs and checks. There are a lot of mov
instructions and no binary loading, no jumps, etc.
At the top of the file/view first instruction is at 0x1DB0
. The moment when shellcode ends is a moment when similarity between IL view and R2R view starts - 0x33B0
.
Let's then carve it out then directly from the file.
$ shellcode.bin (read-only)
acb6c622f2ff6dafdb1391561b26d2abaa3944e342510c6ed2afcc4231cc0680
Shellcode analysis
Because trying to run the shellcode directly on a live system would be of the same level of responsibility as running the Invoke-Expression
or eval
copied directly from the malware (not really responsible and smart) - there are some solutions that enables shellcode emulation.
To be honest it's not even that easy to run or analyze such shellcode, well, because these are just the plain assembly instructions - no PE headers so it is not recognized as "a thing" in dissasemble tools, nor
strings
won't help in our situation,file
command returns just "data".
I'm going to use speakeasy Python module for that purpose.
Quick tip: after git cloning the speakeasy repository and installing requirements, if you would like to call
speakeasy
regardless what you current working directory add following to your shell profile (.bashrc
,.zshrc
) with the correct path of course.export PYTHONPATH="$PYTHONPATH:/opt/speakeasy"
VoilΓ ! It runs, but apparently stops after it verifies that current host is not "ARCH_WORKSTATION-7". Let's change it, shall we?
Finally π
Additional readings