Hack Racing

Posted: 28 March, 2026

Last Edited: 02 April, 2026

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:

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.

A wall of obfuscated JavaScript.

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);
Source code of a list containing the four strings as elaborated upon below.

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:

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.