# Hack Racing

Posted: 28 March, 2026

Last Edited: 01 April, 2026

# Hack Racing

After many delays in releasing this post, I've decided to make this a multi-part series to shamelessly boost my meaningless engagement stats.

I have done my best to make this series digestible to all (also for them sweet engagement stats). So, I hope that this can be informative for seasoned techies, those new to Cybersecurity (like myself), and you normie folk who've somehow made your way here (hopefully not as victims).

It's important to note that this particular attack (and operation) may very well still be active.

Also, I apologise in advance if the formatting of this post is really messed up, I have a lot of work to do overhauling the blog section for converting Markdown to HTML, better metadata tracking, and the lovely CSS that goes along with those changes.

## Prologue

### February 4th, 2026

This story begins like any other major public security story of early 2026: Discord.

I received a message from a friend asking for a quick favour. So naturally, I responded, only to be ghosted for almost exactly 24 hours.

### The Next Day

Aside from the odd behaviour, the hacker didn't account for the possibility that this friend and I might see each other the following day. At which point I was informed that they were hacked.

Eventually, at 24 hours post-hack,

> hi^^ , So I am currently pursuing an IT course where we are developing games. and I developed a simple car racing simulator , As a part of the assignment, I require feedback from people who have played the game. Would you give it a try and provide feedback? It would really help me out and be a big part of my learning!

### Setting the bait

It had to be right. After all, they could read all of our messages, profile me, and discern that I have enough of a background in computing to not fall for such obvious bull. Especially as I know the person outside the digital realm, what they're doing course wise, and what certain subject they're distinctly not carrying on with, which happens to be computer science.

> Sure, I'd love to!

Super sophisticated bait, I know.

> tysm~! , here you go and also if you could give me your feedback in the next 5-10 minutes, let me know if you faced any problem. https://tinyurl.com/Race-Project-IT

Bro literally is trying to live off the stereotypical behaviour of a person with a themed nitro account, and leaves a random comma. Also note the time constraint.

I was also later sent a second gift:

> https://tinyurl.com/Racing-Pro updated

## Epidemiology

This is clearly a token and/or password-stealing trojan, targeting at least Discord.

Until much later, I didn't know that this attacked many, many other services including Steam, Browsers, and Wallets.

Another piece of information gathered by someone else found that the attack is only designed for Windows.

## Static Analysis

Most of this will center around the first trojan, `Race-Project-IT`.

### Links

The links aren't markdown spoofed, but they are obscured by `tinyurl`. Using `expandurl`, I can tell that they go to Discord's CDN, and that I'm supposedly downloading a zip archive. Why on earth they'd make this even more suspicious by providing a tinyurl instead of a link to Discord's CDN on discord, I have no idea.

### VirusTotal

Tossing both binaries into VirusTotal finds... absolutely nothing?

These files were last scanned nearly 24 hours ago as of writing. This could concieveably be the hacker(s) reviewing the quality of their trojan. Other than that, no vendors are screaming alarm bells. The Yomi Hunter sandbox has flagged the second as malware.

I'll force a rescan just to see if one of the vendors that timed out will flag one of them.

And yup. Kapersky managed to not time out during the scan of the first binary, flagging it as `HEUR:Trojan-PSW.Script.Generic`. Kapersky timed out while analyzing the second.

### Strings

I notice a comment string in the binary:

    // feature.h, simple_feature.h, and feature_provider.h..
                            // This feature is only enabled if the embedder's delegated check returns
                            // true.

Searching for these comments leads to mentions of Chromium, maybe more specifically Microsoft Chromium (Edge). I'll keep that in mind.

### The Executable

Extracting the archive reveals a Windows Executable. Breaking down the executable with `binwalk` turns up traces of `electron` and more Chromium, including:

- chrome_100_percent.pak
- chrome_200_percent.pak
- LICENSE.electron.txt
- LICENSES.chromium.html
- app.asar
- Racing_Sim.exe.extracted/A3D5760/image.png

Within the EXE is another `Racing_Sim.exe`, and given the electron image (image.png above) and node modules within it, I suspect this is the actual payload, wrapped in an installer.

Doing a bit of dynamic analysis by checking the behaviour seen in the VirusTotal sandboxes, and then comparing that with the files installed on my friend's system confirms this.

### Metadata

The payload has an interesting bit of metadata:

    Squirrel Aware Version          : 1

With a bit of help from SearXNG, this points me towards the Squirrel.Windows installer project. From here, I can confirm that `Squirrel.Windows` plays a part in this by identifying common metadata listed at https://github.com/Squirrel/Squirrel.Windows/blob/develop/docs/using/custom-squirrel-events-non-cs.md with the metadata on the file:

    File Description                : Racing_Sim
    File Version                    : 2.1.1
    Internal Name                   : Racing_Sim
    Legal Copyright                 : Copyright © 2026 Racing_Sim
    Original File Name              :
    Product Name                    : Racing_Sim
    Product Version                 : 2.1.1.0
    Squirrel Aware Version          : 1
    PDB Modify Date                 : 2023:09:11 20:22:36-04:00
    PDB Age                         : 1
    PDB File Name                   : C:\projects\src\out\Default\electron.exe.pdb

The existence of the PDB metadata leads me to believe that this might be a debug build, but the PDB isn't present.

Reading up on Squirrel.Windows, it uses either `squirrel-pack` or `electron-builder` to package the application. I don't know a lot about the electron ecosystem, but it seems rather clear that I should extract the electron archive.

And there's our target: `4305567d2fbbd1d2.js`

### The Payload?

Unsurprisingly, there's a bunch of node modules present. Some are for interacting with databases, cryptography, and system information gathering.

One thing that peaks my interest is a bit of metadata on the `package.json`:

    Copyright                       : Genesis © 2025

Everything up until now has been crediting the copyright holder as `Racing_Sim`.

#### Who Art Thou, Genesis?

Unsurprisingly, Genesis is a fairly generic name.

But, narrowing the search to "Copyright Genesis 2025 nodejs" brings up a TrendMicro report from November 22, 2023. https://www.trendmicro.com/en_us/research/23/k/attack-signals-possible-return-of-genesis-market.html

But the modus operandi of the Genesis Market was very different, and there's been no news about it since 2023 and parts of 2024. All the dates I've found are 2026, and now 2025.

Trying a cryptocurrency route, there's Genesis Global Capital, a company that went bankrupt in January of 2023. https://www.bankingdive.com/news/genesis-crypto-completes-restructuring-begins-payouts/723330/

There's also `genesiscrypto.org` which has a rather plain site, and no upcoming events. The blog is a bit more interesting, screaming "I'm a get rich quick scam". The last post was "12/18/24", so it's clearly American.

There's also genesiscoin, but I'm getting tired of reading about crypto, and otherwise getting nowhere from this line of inquiry.

If you're going to be a hacker group, at least pick something that you can actually be recognized with. Pick a more distinct name.

For example, if you're going to pick the brand of your father's brother's nephew's cousin's former roommate's car, at least slap a dollar sign on the end! Something!

### Deobfuscating the Payload

Oh... lovely.

Tossing the script into deobfuscate.io makes the linter happy, but not much more.

All content is minified, substituted against a central table (`Ysa8NnK` in this version of the trojan), and control flow flattened, with opaque steps and dead clones seemingly present.

One of the strings in the script is plain base64 encoded, `"bmV3IEZ1bmN0aW9uKCJyZXF1aXJlIiwgZGVjcnlwdGVkKShyZXF1aXJlKTs="` which comes out to:

    new Function("require", decrypted)(require);

Also, there's a very long encrypted string sitting in an array with what appears to be two base64-encoded strings and one that's a neat 32 characters long.

Trying to skip around the control flow flattening, I look for the decryption call. Searching around, I locate the following string:

`aes-256-cbc`

This ought to be fun.

### Breaking Into the Walled Garden

Decryption is handled through the internal `crypto` module.

Working back the components of the AES decryption call finds that the initialization vector is a simple `Buffer` from base64.

    // This has some undone substitution.
    xK7i7T.crypto.pbkdf2Sync(
        w4Matvo,
        (1, xK7Canadiani7T.bHHea4)("GJOvrnu").from(
        avIJtK,
        "base64",
        ),
        1e5,
        c - 157,
        "sha512",
    );

The key decryption (shown above) isn't quite so simple. It's mangled with `pbkdf2` (Password-Based Key Derivation Function 2; I had to double check I hadn't messed up a substitution on that method call...) and integrating a value calculated through all of the control flow flattening.

Brute forcing that value is easy as:

- All numbers in the script are integers.
- Value must be in the range of 0 - 244XX (I forget as of writing; check the docs)
- Cannot be less than 157.

The list containing the keys, initialization vector, and encrypted data is unpacked all at once. However, it is also dastardly obfuscated with ROT1.

Yes, in all 14,000 or so lines of code, they rotated the list entries once.

And with a simple adaptation of the call, I can brute-force the integer:

    const { createDecipheriv, pbkdf2Sync } = require("crypto");
    const { exit } = require("process");

    const algorithm = "aes-256-cbc";

    const inputEncoding = "base64";
    const outputEncoding = "utf8";

    const [keyRoot, base64KeyRoot, base64IvRoot, dataRoot] = [
        "Gcp/3fAC2AjoejyfB73T1xckjDWmMKA4",
        "bx3omGLfT2FpBrmpQ3vGQg==",
        "emZUVlGzS6tC6XAxZ3qQsg==",
        "Encrypted data string here"
    ];

    const iv = Buffer.from(base64IvRoot, inputEncoding);

    function decrypt(c) {
        var key = pbkdf2Sync(
        keyRoot,
        Buffer.from(base64KeyRoot, inputEncoding),
        1e5,
        c - 157,
        "sha512",
        );
        var decipheriv = createDecipheriv(algorithm, key, iv);

        data = decipheriv.update(dataRoot, inputEncoding, outputEncoding);
        data += decipheriv.final(outputEncoding);

        return data;
    }

    for (c = 158; c < Number.MAX_SAFE_INTEGER; c++) {
        try {
        console.log(decrypt(c));
        exit();
        } catch (_) {}
    }

And we're in.

- #AD