Forensics: Oblique Final

Forensics: Oblique Final

Hack The Box CFT Cyber Apocalypse 2024


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)

I will use your initial attention to express my gratitute towards the author of this challenge as it was for me same eye opener as when I discovered the imaginary numbers like 20 years ago, AKA: things that do not exist in your universe until they are revealed to you. To be honest, it's great for the challenges and fun, but that's really scary that such things exists. And for these kind of learning and emotions I love forensics challenges. So thank you c4n0pusπŸ™‡β€β™‚οΈ.

Initial recon

Given: Windows hibernation file.

$ hiberfil.sys (read-only)


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 for python c:/ws/tools/volatility3/

$ 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)

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


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    49829    443            CLOSED    6348    TheGame.exe    2024-02-08 22:43:14.000000

..but again, cul-de-sac. It is easy to establish that 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.

  1. Try loading list of DLLs.

  2. Initilize AMSI engine.

  3. Tests the engine using hardcoded binary data.

  4. Load kernel32.dll test it with AMSI.

  5. If AMSI does not report file as malicious, tries injecting DLL with the same binary data that raises AMSI alert.

  6. Goes limbo with manyNop commands.

Ok, a lot is happening there so let's start with some definitions.

Antimalware Scan Interface (AMSI)
Versatile interface standard that allows the applications and services to integrate with any antimalware product that's present on a machine.
No-operation instruction (nop, no-op)
Machine language instruction and its assembly language mnemonic, programming language statement, or computer protocol command that does nothing.

Although I suspect what that base64 encoded binary data is, let's throw it into CyberChef.

EICAR Anti-Virus Test File
Test file that was developed by the European Institute for Computer Antivirus Research (EICAR) and Computer Antivirus Research Organization (CARO), to test the response of computer antivirus programs. Instead of using real malware, which could cause real damage, this test file allows people to test anti-virus software without having to use a real computer virus.

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).


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 πŸ˜‰



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 set

  • CLI 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: 

rule r2r_assembly
        author = "jiriv"
        description = "Detects dotnet binary compiled as ReadyToRun - form of ahead-of-time (AOT) compilation"
        // 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

Small piece of executable code used as a payload, built to exploit vulnerabilities in a system or carry out malicious commands. Shellcode is commonly written as assembly instructions.

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)

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

Did you find this article valuable?

Support Kamil Gierach-Pacanek by becoming a sponsor. Any amount is appreciated!