Security Through Obfuscation Is Dead

Posted on

My brother vibe coded a country guessing game called Guessterra.

One of its features lets you challenge another player. You finish a game, generate a link, send it to someone else, and they get the same country to guess.

As his brother, naturally, my first thought was not:

“What a nice feature.”

It was:

“I wonder if I can break it.”

A challenge link looked something like this:

https://guessterra.stiuvou.ch/challenge?code=AX9AoA1piAyDX33slpK8vGAUJaAeUpyn

The interesting part was the value after code=.

It looked random, but somewhere inside that string, the game clearly had to store the information to reproduce the challenge. My first thought was it’s probably base64 encoded.

My second thought was that I wanted to see whether GPT-6 Astra could figure out how the format worked.

I didn’t give it any source code. I didn’t explain how the sharing feature had been implemented. I just gave it a handful of challenge codes and told it which countries they represented.

The question was simple:

Can GPT-6 reverse engineer a custom challenge-code format purely from examples?

It turns out the answer is yes.

Starting with a random-looking token

The first example was a challenge for South Korea:

Aa8dX2-WJ2ymvflxyDaOJELEb19QK6oq

Astra recognised that the token looked like Base64URL and decoded it into 24 raw bytes:

01 af 1d 5f 6f 96 27 6c
a6 bd f9 71 c8 36 8e 24
42 c4 6f 5f 50 2b aa 2a

There was no obvious KR, KOR, country ID, or other readable value inside it.

At first there were plenty of possibilities. Maybe the country was encrypted. Maybe the token was just a random database ID. Maybe it contained a hash, nonce, or some other cryptographic structure.

One example wasn’t enough to tell.

So I played the game again to get another challenge for the same country:

AR5AGuxf-9bY7Ky04MVgPHh1MhqBTJvY

The two tokens were completely different apart from the first byte.

That ruled out the simplest possibility: this wasn’t just the country encoded in some deterministic way.

I then supplied more examples, including Germany and the United Kingdom by playing the game again.

For a while, they still looked mostly like random binary data.

“Keep trying out things”

GPT-6 Astra had explored a few plausible explanations, but none of them had worked yet and it had already given up.

My extremely sophisticated contribution to the reverse-engineering process was:

“keep trying out things”

And surprisingly, that turned out to be useful advice.

Instead of concluding that the value was encrypted or otherwise inaccessible, Astra kept looking for relationships between different parts of the token.

That produced the first real breakthrough.

The checksum

The final four bytes weren’t random.

They matched the first four bytes of the SHA-256 hash of the preceding 20 bytes:

checksum = SHA256(bytes[0:20])[0:4]

So the token had a definite internal structure:

[ 20-byte payload ][ 4-byte checksum ]

That didn’t reveal the country yet, but it was an important clue.

The challenge code wasn’t just an opaque blob anymore. It was a small custom binary format.

The next discovery by Astra was the most interesting one.

Bytes 1 through 16 appeared to contain random data. Astra started checking whether the remaining bytes were somehow related to that random section.

For the first South Korea token, it XORed bytes 17 through 19 with bytes 1 through 3:

c4 6f 5f
XOR
af 1d 5f
=
6b 72 00

In ASCII:

6b = k
72 = r

So the result was:

kr 00

Interesting.

But one match could still be coincidence.

So Astra tried the completely different second South Korea challenge:

75 32 1a
XOR
1e 40 1a
=
6b 72 00

Again:

kr 00

That was the point where the scheme effectively fell apart.

Checking other countries

Germany produced:

e3 1a d1
XOR
87 7f d1
=
64 65 00

64 65 is ASCII for:

de

The United Kingdom produced:

19 68 c1
XOR
7e 0a c1
=
67 62 00

Which gives:

gb

That was an especially nice confirmation because ISO 3166-1 uses GB, not UK.

At this point the structure was pretty clear:

byte 0      version
bytes 1–16  16 bytes of random data
bytes 17–19 XOR-masked values
bytes 20–23 checksum

The first two masked values could be recovered with:

country_letter_1 = byte[17] XOR byte[1]
country_letter_2 = byte[18] XOR byte[2]

Those two bytes form the lowercase ISO country code.

The real test: unknown challenges

Finding a pattern in examples where you already know the answer is one thing.

The real test was whether the same method could decode completely new challenges without knowing the countries beforehand.

So I started sending fresh links.

One decoded to:

KE

Kenya.

Another:

BR

Brazil.

And another:

ID

Indonesia.

All of them also passed the SHA-256 checksum test and it was the right answer inside the game.

At that point, it wasn’t just a theory. Astra had built a working decoder.

Why the obfuscation doesn’t protect the answer

The country is XORed with random-looking bytes.

That sounds vaguely cryptographic until you realise that the bytes needed to reverse the XOR are included in the exact same token.

Conceptually, the format does this:

hidden_country = country XOR mask

But then sends both:

hidden_country
mask

Which means anyone who understands the format can simply calculate:

country = hidden_country XOR mask

The SHA-256 checksum makes it possible to detect whether the token has been modified, but it doesn’t make the contents secret.

There is no secret key involved.

It’s obfuscation, not encryption.

The part I found most interesting

The XOR itself isn’t particularly exotic.

What surprised me was how little information Astra needed to work all of this out.

It had:

  • a handful of opaque challenge strings;
  • the countries corresponding to some of them;
  • no source code;
  • no documentation;
  • and repeated encouragement to keep experimenting.

From that, it identified the transport encoding, found structure in the binary data, discovered the checksum, tested different hypotheses, found the XOR relationship, and successfully decoded previously unseen examples.

Small custom formats used to have a certain amount of protection simply because understanding them was annoying and time-consuming.

That barrier is getting much smaller.

Conclusion

What started as me trying to cheat at my brother’s country guessing game turned into a surprisingly fun reverse-engineering experiment.

The challenge link looked like a random string:

code=AX9AoA1piAyDX33slpK8vGAUJaAeUpyn

But underneath it was a small binary format containing random data, an XOR-masked ISO country code, another masked byte, and a truncated SHA-256 checksum.

The country wasn’t really encrypted. It was just hidden well enough that a human looking at the URL wouldn’t immediately recognise it.

And apparently, in 2026, getting past that can require little more than a few examples and the instruction:

“keep trying out things.”

Security through obfuscation is dead, killed by AI.

One thought on “Security Through Obfuscation Is Dead

  1. My uncle tried this same link sharing thing and the security settings on his phone blocked it immediately. That lack of privacy is a major issue.

Leave a Reply

Your email address will not be published. Required fields are marked *