Chainsaw: Reverse Engineering Grey Hack's Password Generator

Chainsaw splash screen

Grey Hack is a multiplayer hacking simulation game. The kind where you're sitting inside a terminal, SSHing into strangers' machines, stealing files, covering your tracks. To keep the world feeling alive, the game procedurally generates practically everything: domain names, character names, and passwords, for a practically infinite number of NPC networks. And for a while, I had a nagging feeling about how it was doing it.

Something Felt Off About the Passwords

The passwords were the tell. Some of them were almost too mundane: gold, russiant. But then you'd crack open a machine and find something like weeterso, or asurence, or my personal favorite, dessnap. Gibberish, but not random gibberish. They felt too close to real words every now and then to be random.

That pattern is the fingerprint of a Markov chain: a generator that holds statistical relationships between characters from a training corpus, then produces new sequences by following those probabilities. Common real-word transitions appear frequently, rare ones don't. The output looks plausible, most of the time anyway.

Cracking open the game's assemblies in dnSpy confirmed the suspicion. The word generator was indeed Markov-based and was pulling from some bundled text file but the asset wasn't directly readable. I knew what I was looking for, though: a large, real-world password list. A quick search for "big password list" turned up RockYou, a dataset of 14 million plaintext passwords leaked in a 2009 data breach that has since become a standard fixture in security research. I'd never heard of it before that moment. I slotted it into an early prototype and ran some tests. And, to quote Todd Howard, "It just works."

The confirmation came later, when I did a full asset dump using Unity AssetRipper. There it was, RockYou, labeled simply Passwords.

How Password Cracking Worked Before

In Grey Hack, NPC machines store passwords as MD5 hashes in a local password file. The game provides a built-in tool called decipher to recover the plaintext, but it comes with strings attached: you need filesystem access, read permissions, and then you sit through an artificial timer while your in-game CPU grinds through it. At the end of that timer, the game makes an RPC call to just fetch the answer from the server. The timer is bullshit.

Community tools had improved on this. Brute-forcers, dictionary attacks, rainbow tables... but they were all working around the problem rather than through it. And, to put it plainly, they sucked. At least for the specific application I had in mind. It needed to be fast, and automatable. None of those tools understood what the passwords actually were, either. They were searching blindly through spaces the game would never generate.

Reimplementing the Generator

The core insight behind Chainsaw is straightforward: if the game generates passwords using a Markov chain trained on RockYou, then an identical Markov chain trained on the same corpus covers the entire attack space. Every password the game can produce, Chainsaw can produce, by definition.

The implementation itself is recursive chain traversal. Build the trigram transitions from the corpus, pick a starting state, walk the chain, and at each step check whether the current candidate is the target password. In Grey Hack's scripting environment, MiniScript/GreyScript, that check is a simple get_shell() call. This is fantastic for our purposes, because it requires no network call to external servers, just local evaluation, which means attempts are, for practical purposes, instantaneous.

The language itself wasn't the hard part. MiniScript is limited but manageable for recursive logic. Getting parameter parsing to behave reasonably from an in-game terminal sucked, frankly. Anyone who's looked at the source will notice. The more interesting challenge was making the result usable from other scripts running on entirely different in-game machines. The solution was get_custom_object (GCO), a Grey Hack mechanism for passing objects between scripts. Chainsaw loads itself into GCO on initialization, and a calling script on a remote machine can reach in and pull the resulting shell object with a simple RCE string:


        ChainSaw.load  // Load Chainsaw into get_custom_object

        rce_string = "
        chainsaw = get_custom_object.chainsaw
        chainsaw.init()
        chainsaw.crack()
        "

That design decision of making it trivially composable into other tools started as one of necessity. I needed to be able to call it from my log scraping tool.

Community Adoption, Slowly

Chainsaw went up on GitHub in May 2025. For months, almost nobody noticed. A friend of mine in the community, 0x0, integrated it into his own hack tool and credited it by name, which helped (and even bundled the license into the game file!). The GitHub link embedded in the splash screen every time it runs didn't hurt either.

I didn't make a proper public announcement until November 2025, and only because someone else forced my hand. I found another player had lifted the code wholesale into their own tool without attribution. After a gentle reminder that MIT just requires you keep the license file, I figured I might as well introduce it properly. I posted in the Grey Hack Discord's project channel: "chainsaw — The end-all Markov-Chain password cracker."

Since then it's spread quietly, like a useful tool. Copycats, integrations, the occasional Steam guide mention. It's hard to measure exactly how much use it gets, the design intentionally makes it easy to copy-paste directly into the game without any formal install, but posts referencing it have been showing up more frequently since early 2026. Someone else's project listing it as a dependency is a terrific compliment (just include the goddamn license!).

Does It Actually Work? The Benchmarks

"100% success rate" is a bold claim. It's also, in this case, true by definition. Because Chainsaw is an implementation of the same generator the game uses, with the same parameters and the same corpus, it covers the full attack space by construction. If the game could produce a given password, Chainsaw will eventually produce it too. For the most unfortunate trigram combinations, "eventually" might take a moment. But it gets there.

That said, I recently ran a proper benchmark. 35,000 passwords from a pregenerated corpus, tested across four method variants, partly to settle a question that came up in the Grey Hack Discord about whether frequency-weighted ordering could improve performance.

The short answer: one optimization is real, one is a statistical illusion, and neither matters that much.

Trigram sorting — reordering chain entry points by how frequently each trigram appears in the corpus, drops the median attempt count from ~350,000 to ~312,000, a roughly 10–11% improvement, and outperforms flat ordering on about 53% of passwords.

Transition sorting — reordering successor characters within each trigram state by their observed frequency, is essentially a no-op. Median attempt count is identical to flat. The reason turns out to be a property of how the chain gets built: because RockYou is itself a frequency-ordered dataset, common words appear repeatedly during construction, and their trigrams accumulate in roughly descending frequency order as a side effect. Only 183 out of 1,999 trigram states are actually out of frequency order in the default flat chain, and most of those are near-ties anyway. There's almost nothing to sort!

The practical upshot: trigram sorting offers a modest improvement, transition sorting offers nothing, and the question of whether either is worth the startup cost in MiniScript's artificially throttled execution environment is probably no. For a one-off crack, which is how most people use it, the attempt reduction might not offset the sort overhead. For a persistent or pre-initialized context, trigram sorting is probably worth having.

VROOM

There's a pattern I keep finding in Grey Hack projects that involve solving the game's procedurally generated content. First, a brute-force approach, implemented roughly. Then optimization. CUDA kernels, shortcuts in the search space, smarter construction. Then, if you're lucky, some non-obvious shortcut that blows the whole thing open.

Chainsaw never had that third step. There isn't one. Trigram sorting, transition sorting they only improve things in the academic sense. But the tool just works, for what it needs to do. There's no cleverer angle to approach it from, because the approach is already optimal by construction. You see the splash screen, it says VROOM, and it will get the password, as fast as it can realistically go.

Documents