Challenge files
The archive contains a single file, generated.py. At the top
it defines a stack, sets up a high-precision Decimal context,
and exposes four tiny operations:
stack = []
def E():
a = stack.pop()
b = stack.pop()
stack.append((Decimal(0.0) if a == OMEGA else CTX.exp(a)) - CTX.ln(b))
def s():
stack[-1], stack[-2] = stack[-2], stack[-1]
def one():
stack.append(Decimal('1'))
def push_inp():
stack.append(Decimal(ord(inp[0]) - 48))
The title is mostly bait. Yes, ln is the natural logarithm and
exp is its inverse, but in this challenge they are just being used
as a weird instruction set to hide a much simpler verifier.
First pass
The body of the script is just thousands of repeated calls to
one(), s(), neg_inf(), and E(), with
a push_inp() every so often. The fast sanity check is to count
those push_inp() calls: there are 32 of them, which strongly suggests
a 32-character flag.
The other useful observation is that the script keeps rebuilding the same
constants. In particular, 73.21 shows up over and over, and the end of
the file compares the computed values against four large constants.
Reducing the verifier
Instead of trying to symbolically simplify every VM instruction, I traced the stack at the boundaries between the repeated blocks. That makes the shape of the computation obvious: each group of eight input characters is turned into a single decimal value and kept on the stack.
Each 8-character chunk is evaluated as
sum((ord(ch) - 48) * 73.21**i for i in 1..8).
So the enormous generated file is equivalent to checking four equations of the form:
chunk_value = sum(
(ord(ch) - 48) * Decimal("73.21") ** i
for i, ch in enumerate(chunk, 1)
)
At the end, the script compares those four chunk values against these targets:
46741716782375706.8396419575653316
46291277424349185.5548286712719316
42149201139278358.4223548171552311
64147886106222656.2384332732886897
Recovering the flag
Once the problem is in that form, the rest is just base recovery over the
expected alphabet. The challenge description already gives the flag format as
ping{.*}, which fixes the start and end of the first and last chunks.
Searching over the alphabet _0123456789abcdefghijklmnopqrstuvwxyz{}
yields exactly one solution per chunk:
ping{1_h
473_m47h
___17_5c
4r35_me}
Concatenating them gives the final flag.
Solver sketch
from decimal import Decimal
BASE = Decimal("73.21")
targets = [
Decimal("46741716782375706.8396419575653316"),
Decimal("46291277424349185.5548286712719316"),
Decimal("42149201139278358.4223548171552311"),
Decimal("64147886106222656.2384332732886897"),
]
def chunk_value(chunk: str) -> Decimal:
return sum((ord(ch) - 48) * (BASE ** i) for i, ch in enumerate(chunk, 1))
Downloads
I uploaded the helper files used for the writeup so the whole solve is reproducible from the blog post itself.
solve.py
The backtracking solver used to recover the four 8-character chunks from the target constants.
Download solverdeobfuscated-checker.py
A clean equivalent checker that verifies a candidate flag without the
generated exp/ln stack-machine noise.
notes.md
Short notes with the alphabet, base, targets, chunk breakdown, and final recovered flag.
Download notesFlag
ping{1_h473_m47h___17_5c4r35_me}