First look#
This one came with a very on-theme setup: a tiny CHALL.EXE DOS program and a floppy image containing the same executable. A quick strings pass already told me it was a flag checker:
| |
So the question was not what the binary did, but whether it hid the flag in a way that was annoying enough to matter.
What the checker really does#
Loading the program in radare2 as 16-bit x86 made the structure fairly clear.
The first part is just a prefix check. The binary rejects anything that does not begin with lactf{, so there is nothing interesting there.
The second part is where the actual trick lives. The checker hashes the entire input into a 20-bit state:
| |
That 20-bit value becomes the seed for a small LFSR:
| |
The checker advances the LFSR once per character, takes the low byte, XORs it with the candidate flag byte, and compares the result against a 73-byte constant stored in the data segment.
So the validation logic is:
| |
At first glance that looks circular, because the input determines the seed and the seed determines the keystream that decrypts the input.
The weakness#
The circular dependency looks clever, but the state is only 20 bits wide. That is just 2^20 possibilities, which is completely brute-forceable.
So instead of trying to solve the algebra directly, I treated the embedded bytes as ciphertext and tested every possible seed:
- Generate the LFSR keystream for a candidate seed.
- XOR it with the stored bytes to recover a plaintext candidate.
- Keep only printable candidates that look like
lactf{...}. - Re-hash that plaintext and check whether it reproduces the same seed.
That last check is what resolves the circular dependency cleanly.
Solving it#
The full brute-force script is short:
| |
The correct seed turned out to be 0xf3fb5, and the recovered plaintext was the flag.
Flag#
| |
Takeaway#
The interesting part here was not the DOS binary itself. It was noticing that the fancy self-seeded stream cipher still collapsed to a tiny brute-force space. Once I stopped treating the seed dependency as a blocker, the solve became a straightforward offline search.