-->-->the "authoritative" game coordinator that wasn't
-->
-->-->-->-->--><br>AuthorsNamealulaTwitter@__alula<br>reverse engineer<br>tl;dr
i factored a real 509-bit RSA key by turning a few friends’ gaming PCs into a weekend supercomputer, then used it to decrypt an entire backend protocol off the wire against a completely unmodified client. the game it belongs to is tower unite, but the gaming is incidental. this is a story about breaking hand-rolled crypto with actual number theory, and it’s worth your time even if you’ve never touched the game.
the short version: tower unite’s backend wraps its AES-256 session key in its own homemade RSA. the modulus was sitting in the binary at 509 bits, small enough to factor at home with cado-nfs and the general number field sieve. about a day and a half of compute later i had the private key, and from there the session key from any handshake, and from there the entire decrypted conversation.
the crypto was broken three separate ways: a key generator that’s a toy, a static key it actually shipped, and a decrypt routine that leaks uninitialized heap. each is its own little lesson in why you don’t roll your own. i reported it, and pixeltail, a small indie studio, ripped the whole homemade layer out and replaced it with secp256k1 and libsodium inside two weeks, which is genuinely impressive. the one gap they left is server authentication, so an active man-in-the-middle is still on the table.
this is a ud2.rip post. the math, the bugs, and the lessons are all here. the working tooling is not, for reasons i’ll get to.
how i got here
i didn’t set out to break any crypto. i set out to run a server.
back in 2023 i’d been poking at AGC, the thing tower unite uses to talk to its backend, mostly to see what was editable and what was cheatable. that’s when i first noticed the key generation looked suspicious. i filed it under “funny, revisit later” and moved on.
the “later” arrived in august 2024. pixeltail had been talking about community-hosted dedicated condos in their dev updates, and they had their own dedicated condo sitting on the server list. i wanted one running locally, which meant getting the AGC side of things to cooperate.
it did not cooperate. my dedicated server’s logs filled up with AGC connection failures, over and over. so i started looking at how a server is even supposed to connect to AGC, which is the thread that pulled me into the rest of this.
the role system is where it got interesting. AGC tags every connection with a role: the player client, an official pixeltail “tower server” (the plazas and game hubs), a community-hosted dedicated condo server, or AGC itself. the authentication is completely different depending on which one you claim to be. a client or a community condo server has to present a steam auth ticket. an official tower server presents a role number and a steam id, and that’s the whole story. no ticket, no password, no encryption. the only thing that could plausibly be gating those connections is an IP allowlist on pixeltail’s side.
so now i had two questions: could i convince AGC i was a server, and what could i reach once i was talking to it. while staring at the handshake trying to answer the first one, i realized i could man-in-the-middle it. that was the moment the project stopped being “run a server” and became “okay, how broken is this.”
what even is AGC
AGC stands for Authoritative Game Coordinator. it’s pixeltail’s own name for it, there’s a wiki page and everything.
it is not the realtime game server. the actual gameplay runs on unreal engine 4 dedicated servers, and another chunk of the backend is plain php sitting at backend.towerunite.com. AGC is the out-of-band piece. clients stay connected to it the entire time they’re playing, the game servers connect to it too, and it’s how everyone gets notified about backend events. it owns persistence and economy: your XP, your units, your inventory, achievements, the store, fishing, server listings, all of that.
internally it’s called something else. the binary leaks its own source paths in .rodata:
/home/robert/Desktop/Git/CasinoBackend/src/agc_lib/clientmessages.cpp<br>/home/robert/Desktop/Git/CasinoBackend/src/agc_shared/bitstream.cpp<br>/home/robert/Desktop/Git/CasinoBackend/src/agc_shared/netmessages.cpp<br>so it’s “CasinoBackend”, it’s C++, and it’s split into a client library (agc_lib, the thing shipped inside the game) and a shared protocol layer (agc_shared).
on the wire it’s a custom, bit-packed, very stateful RPC protocol. a connection roughly goes:
the client sends Connect with its steam id, a steam auth ticket, and the AES session key (this is the bit RSA is supposed to protect).
the server replies ConnectAck.
the server sends a NameTable, which is a dictionary of every identifier it’s about to use (achievement, getfunds, createcondo, …), packed six bits per character.
the server sends GameClass definitions. each one is a little RPC interface: a list of events (methods)...