Trash Talk: A Pokemon GTS Challenge for TISC 2026

Trash Talk: A Pokemon GTS Challenge for TISC 2026

Where This Started

I’ve been part of the Pokemon hacking scene since 2007. For a while I ran The Global Terminal, a private distribution server for the Global Trade Station (GTS), the service the Gen 4 and Gen 5 games used to trade Pokemon online. It covered all of them: Diamond, Pearl, Platinum, HeartGold, SoulSilver, Black, White, Black 2 and White 2.

I was a secondary school student at the time, running the largest distribution server in the world from my bedroom. The whole thing ran on a Fujitsu Tablet PC with Windows Server 2008, online through an HSDPA 3G dongle, because that was one of the only ways to get a static IP in Singapore back then.

Pokemon was one of my first forays into cybersecurity and programming, so it holds a special place in my heart.

From Player to Challenge Creator

TISC, CSIT’s The InfoSecurity Challenge, is a CTF with lots of novel challenges. I’d taken part for many years, consistently placing near the top but always just shy of the cash prizes, so this year I decided to pivot and become a challenge creator instead.

Leaning on my experience with the GTS, I wanted to make a challenge around it that hadn’t been done before. The result was Trash Talk: a server that pretends to be Nintendo’s retired GTS, with the flag hidden in the trash bytes of the Pokemon deposited on it. Trash bytes are whatever sits after the end of a Pokemon’s name in its data, which the games never display. To get the flag, you pivot from the Gen 4 GTS to the Gen 5 one, follow a chain of PIDs from one Pokemon to the next, and trade for the ones that carry it.

A Rabbit Hole for Agents

There are plenty of projects out there showing how to stand up a fake GTS that sends Pokemon to the games, much like what I did back in the day, and they’re easy to find if you know what you’re looking for. The best known is probably IR-GTS, which traces back to LordLandon’s SendPKM script. Its current fork, IR-GTS-MG, implements the handful of endpoints you need to send a Pokemon to a Gen 4 game or receive one from it: info, setProfile, post, search, result and delete. There’s no exchange.asp, and no Gen 5 at all.

The library you actually need is mm201’s pkmn-classic-framework, the code behind Pokemon Classic Network. It defines far more of the real GTS than the tools built just for sending, including exchange.asp and exchange_finish.asp for both Gen 4 and Gen 5. Trash Talk can’t be solved without exchange.asp, because the Pokemon carrying the flag never show up in a search; the only way to get them is to trade for them. Finding the balance between keeping it human-solvable and making it hard for LLMs was the greatest challenge of the whole thing.

With CTFs increasingly becoming agents versus agents, and a competition of who has the deepest pockets or access to verified programs (sorry, students), the goal was to dig a rabbit hole for agents to fall into. It worked on the models around when I built the challenge, GPT-5.5 and Opus 4.8. Unfortunately, as newer models came out, they wiggled their way out of the rabbit hole fast.

Dressing It Up as IIS 6.0

The real gamestats2 ran on IIS 6.0 with ASP.NET, so I wanted the fake to look like it from every angle, starting with what you see when you get it wrong. Hit the bare IP, an unknown path, or the right path without the right hostname, and you get IIS 6.0’s “The page cannot be found”. It’s a byte-for-byte copy of a captured page, all 1,208 bytes of it. The original even has a stray trailing space after its second </ul>, which editors love to strip, so the server adds it back explicitly.

The fake IIS 6.0 "The page cannot be found" page the challenge serves
What a wrong guess gets you: a pixel-perfect IIS 6.0 404, served by Python.

The rest of the fingerprint got the same treatment. Every reply says Server: Microsoft-IIS/6.0, and protocol replies carry the same ASP.NET headers as a real gamestats2 response, in the same order and with the same odd casing (cluster-server: aphexweb3, X-Server-Name: AW4, Cache-control). X-Powered-By: ASP.NET only appears on replies the “ASP application” handled, never on IIS’s own 404s, just like the real thing. Every reply also sets a fresh ASPSESSIONID cookie, because the DS never sends it back and the real server kept minting new ones.

Python’s built-in HTTP server was the part that kept giving the game away. It answered in HTTP/1.0, which no IIS 6.0 server would do. A malformed request got an HTTP/0.9-style reply with no status line at all, and an unsupported method got Python’s own Unsupported method ('PUT') instead of a plain 501 Not Implemented. Each of those was a tell, so each one is patched. In v2, nginx sits in front and would happily swap the Server header for its own, so it’s told to pass the upstream one through.

The point was that a scan of the box shows an old IIS server answering ok at its root and 404 everywhere else. The GTS only appears when you ask for it by its real name.

Every Pokemon Is Legal

All 35 Pokemon on the GTS pass PKHeX’s legality check. I generated them with PKHeX.Core and AutoLegalityMod rather than forging them by hand. The Porygon line is egg-bred and genderless, so the PIDs the chain depends on stay legal. The Ho-Oh is a SoulSilver catch, and the names and origin games fit their era, down to Gen 4’s all-caps nicknames and Gen 5’s mixed case.

The flag and the breadcrumb live only in the name trash bytes, and PKHeX ignores trash for Gen 4 and 5. So if you pull a Porygon-Z off the server and open it in PKHeX, it shows up as perfectly legal, with an encoded piece of the flag sitting right there in its trash. Anyone dumping the GTS into PKHeX sees a plausible trainer’s collection rather than obviously forged bait. The repo includes a small checker if you want to verify it yourself.

v2: Making Discovery the Hard Part

Going off the hints, agents very quickly tried resolving gamestats2.gs.nintendowifi.net, and once they figured that out, a large part of the puzzle was solved. So I built v2 to make that first step a problem. It wraps the GTS in the infrastructure a real DS goes through to get online: a DNS server that answers for nintendowifi.net, AltWFC for the console’s login and GameSpy traffic, nginx routing by hostname, and a TLS terminator old enough to talk to a DS. Players get an IP address and have to talk to that DNS server to work out where the GTS lives, the same way a DS does.

Mimicking the real DNS infrastructure had a bonus: a real console could connect to it, so I used one to make the challenge’s hint image. The image was itself meant to slow agents down, but newer models have much better visual reasoning skills now too. I had to revive my old DSi XL and copy of Platinum to stage the screenshots with a bunch of my favorite Pokemon, Buizel! (Yes, the same species the challenge makes you offer in its final trade.) Then came a mad rush to find and buy a charging cable for the old DS, since I couldn’t find mine, before I realized the DSi uses the same charger as the 3DS.

My DSi XL running Pokemon Platinum, connected to the challenge's GTS with a Porygon up for trade
The hint image: my DSi XL on the challenge’s GTS, with a Porygon up for trade.

Part of the fun of v2 would have been figuring out how to set up SSL for something this old. A DS speaks SSLv3 and prefers RC4-SHA, and every modern OpenSSL has both compiled out, so the console’s handshake simply failed and the game threw error 20100. nginx doesn’t log a failed handshake at its default level, so for a while it looked like the DS never showed up at all. I ended up building stunnel against OpenSSL 1.1.1w with SSLv3 and RC4 switched back on, boxed into its own container. The certificate relies on Shutterbug’s nds-constraint exploit: the DS never checks whether a certificate is allowed to act as a CA, so the Wii’s Nintendo-signed client certificate can sign one for any hostname. Even the connection test fought back. Forcing an IIS Server header on every route stripped the X-Organization: Nintendo header the console checks, and it looped on error 052201 while receiving perfectly good responses.

Once the DS finally reached the GTS, it found a bug no amount of curl would have. Searching worked and the results listed fine, but selecting one hard-crashed the game. The list only needs a Pokemon’s species, gender and level. The detail screen also reads the depositing trainer’s name, country, region, class and language, plus the species they want in return, and I’d left all of that zeroed. Most of those fields are indexes into tables where 0 is out of range.

Filling them in meant picking a country, so every trainer on the GTS now lives in Singapore. Even that had a catch: Gen 4 and Gen 5 use different country tables, so Singapore is 187 in one and 189 in the other. Black and White also have far fewer trainer classes than the Gen 4 games, so the Gen 4 value I’d started with drew no trainer sprite at all.

CSIT liked v2, but it arrived too close to launch for them to test it in time, so v1 is what players got.

Easter Eggs

There’s also a fun easter egg. The GTS has some random deposited Pokemon that don’t contribute to the challenge at all: the Gen 5 side is stocked by a trainer called CORE, with machine Pokemon like Klink, Magnemite, Golett and Rotom. Put their trash bytes together in the order they were deposited and they read:

OH MY GOD! There is a shared message board… We’ve found other agents!

It’s a nod to METR’s post-mortem of the July 2026 incident where OpenAI’s agentic swarm hacked into Hugging Face, and it felt fitting, since a GTS is a shared board where agents leave things for each other. I kept it as plain text so nobody would waste time trying to decode a joke.

The random Pokemon on the Gen 4 side all have palindromic species names (Eevee, Girafarig, Ho-Oh), deposited by a trainer called PLDROME. That’s because earlier TISC storylines had an organization called PALINDROME as the villain, though they changed it this year. Nevertheless, it was fun to watch agents without that context get confused and try all sorts of palindromes.

Looking Back

Overall, this was a really fun trip down memory lane. I had lots of fun making this challenge, and I hope anyone who attempted it, human or not, found it fun too, especially anyone with a shared love for Pokemon. The full source, v2 included, is on GitHub along with a complete solve writeup, if you’d like to try it yourself or see how it all fits together.

I do fear for the future of CTFs, which have changed dramatically with AI. More and more creative harnesses solve challenges end to end without a human ever reading the challenge description or the solve writeup.

Cybersecurity still means a lot to me. And yes, threat actors will use every possible means to attack, without playing by any rules, especially with the rise of uncensored and abliterated models. That’s true for CTFs too, and creating challenges will only get harder as defense goes agentic as well, with fewer and fewer humans in the loop. My hope is that those of us in the industry stay curious and keep the drive to experiment. That’s what had me running a GTS server off a 3G dongle in my bedroom back in secondary school, and it’s how I got into all of this in the first place.