First reaction#
The challenge description tells you, very politely, not to solve it the obvious way:
don’t interpret the puzzle, it will OOM your computer
That was accurate. The provided interpreter can start reducing the source program, but doing the whole thing through Church-encoded lambda calculus is hopelessly slow. So from the beginning, the real solve was always going to be static analysis.
Recognizing the language#
The first definitions in flag_riddle.txt give the theme away:
| |
Those are just Church booleans and the identity function written with Chinese tokens. Once that clicked, the file stopped looking like an unknown esolang and started looking like a parser problem.
The core grammar is compact:
| Token | Meaning |
|---|---|
以...而为 | function definition |
于 | application |
为 | binding |
矣 | end of statement |
Confirming it in the binary#
I still checked the interpreter in IDA to make sure the source-language guess matched reality.
The decompilation confirmed a normal lambda-calculus interpreter with three node types:
| Type | Meaning |
|---|---|
0 | variable reference |
1 | lambda abstraction |
2 | application |
The output path was also revealing. The interpreter walks a Church-encoded linked list, converts one Church numeral at a time into an integer by counting f applications, writes the corresponding byte, then advances to the tail.
That was the key mental shift: I did not need to evaluate the lambda calculus directly. I only needed to recover the arithmetic expression graph that eventually produced those numerals.
Turning the source into ordinary data#
A few encodings matter:
朝...暮wraps binary literals春means bit0秋means bit1- bits are read least-significant-bit first
So something like:
| |
represents 1 + 0 + 4 = 5.
The flag itself is stored as a linked list built from Church-style helpers such as 双, 有, 无, 本, 末, 在, and 用. Once I parsed the 旗 definition, I had the exact order of the variables that corresponded to flag characters.
The remaining work was evaluating the definitions that produced those variables.
The important operator mapping#
The place I could have gone wrong was the arithmetic vocabulary.
The critical discovery was:
销is subtraction次is multiplication
The sanity check that made this clear was one of the data chains:
| |
That only makes sense if 销 means subtraction. After that, the rest of the numeric expressions started falling into place.
The nice design choice in the challenge is that it uses huge operations like factorial without making the final values huge. Terms such as 32! / 31! collapse cleanly back to small integers, so a plain Python evaluator with big integers is enough.
Solver#
My static solver did three things:
- strip away non-CJK wrapper text,
- parse each definition into a tiny expression DAG,
- evaluate literals, add/sub/mul/div/pow/factorial recursively with memoization.
This was enough:
| |
Flag#
| |
The program continues with extra Chinese text after the closing brace, but the ASCII substring above is the actual flag.
Takeaway#
I liked this challenge because the “esoteric interpreter” part is mostly there to scare you into doing too much work. Once the syntax and operator mapping were clear, the right move was to throw away the interpreter and treat the source as serialized arithmetic.