
New to CTFs? Check out WarmupCTF.
Much like everything in the world, AI has completely changed the way CTFs need to be designed. Challenges that used to be hard are now easy. As one example, my favorite trick used to be xor encoding flags in executables using a fake flag like cyber{lolnottheflag!} and seemingly random binary data to xor to the actual flag.
The logic is that there should be an easier way to get the flag, but if someone wants to take the time to figure out the xor encoding, more power to them. The world changed though when LLMs like ChatGPT became able to do what is called static analysis. That is, literally interpret the static code as hex and decode the file into instructions and run through the logic of the executable.
This lead me to work towards creating a new breed of CTF challenges, so called “AI Resistant” challenges. Using a “if you can’t beat them, join them” mentality, I paid for subscriptions to Anthropic’s Claude and OpenAI’s ChatGPT and pit them against each other to attempt to make AI challenges they would get either out right wrong or challenges they simply could not do.
The results were mixed and many of the challenges were eaten up by AI quickly, nonetheless. More on that in another blog post, but I wanted to highlight one challenge that pretty much stopped AI in its tracks. DEC.
DEC stands for Digital Equipment Corporation which was an very important computer company from 1960s to the 1990s. A lot of students confused DEC with decimal, interestingly enough, which made the challenge all the more difficult.
The idea for this challenge came from the recent announcement that an original tape of UNIX v4 (which had thought to been lost to time) had been found. I downloaded the source code and, on a lark, was curious if the executables had a magic number. Turns out, they did!
This seemed like a good start to a CTF challenge and I toyed with idea of mixing octal (which was very popular at DEC once upon a time) and hexadecimal. I quickly realized that hexadecimal or octal, I still had to encode a flag in ASCII, so that route wasn’t going to confuse AI.
Then I remember that ASCII is actually a 7 bit encoding, not an 8 bit encoding. The highest most bit has been used for various things over the years, but encoding cyber{some_kind_of_flag} does not require 8 bits.
I thought to myself, what I can do with that spare bit? It hit me! Bit packing! You see, in the old days every bit counted (as I found out when I tried to sneak a flag into the ROM of Pitfall Atari 2600 ROM… not even a length of 4 bytes of 00s to play with!), so one of the tricks was to encode things across byte boundaries.
I then used the magic numbers I found on the UNIX v6 executable and asked Claude to create a fake PDP11 executable around it.
As an aside, I heard a few students tried to run the PDP11 executable, which couldn’t possibly work as it was fake. One of my core values in CTF writing is I never lie, so when I wrote you can’t run this, I really meant it!
It took over 12 hours for the NJ students, well armed with AI, to solve this challenge. Taking a fresh look at it, here’s how I would have solved it.
First, download the (fake) executable
The second thing to do is xxd dec.

The first thing to see is the PDP11 magic number, but the challenge insists decode not run. Looking at the data, there are a lot of zeros everywhere (which is normal in an executable), but quite oddly there is one line that doesn’t line up at memory address 0x00000010.
A quick run of hex to binary leads to gibberish, but something nags at me that there is data to be found here and I remember known plaintext.
What we mean by known plaintext is that we know, with hopefully rare exceptions (especially in OSINT challenges), that the flag will always start with something like cyber{.
Let us look at the binary for cyber{:01100011 01111001 01100010 01100101 01110010 01111011 00001010
Let us also look at the binary for the weird hex c7e7 165e at memory address 0x0000010:11000111111001110001011001011110
So we’re looking for c which is 01100011 and we start with 1100011. It isn’t too hard to see that the leading 0 has been left out. Now we look for y which is 01111001 and see we have 1111001. Repeat as necessary, and you get the flag:
cyber{lucky_seven}
Bit packing particularly stumps AI because it has no way to know the hex code is shifted. One wonders how long it will take before AI figures out that trick, too.