reversefreehard

nuclear_codes

umdctf

Task: a stripped PIE ELF asks for three positive integers with a > b > c and validates them through MPFR/Arb-style interval arithmetic. Solution: reverse the interval wrappers, simplify the final comparison to a rational cubic, find a rational point in the valid region via an elliptic-curve transform, and submit the resulting exact integer triple.

$ ls tags/ techniques/
dynamic_wrapper_recoveryinterval_compare_reversalsymbolic_algebraic_simplificationrational_cubic_solvingelliptic_curve_search

nuclear_codes — UMDCTF

Description

No separate organizer description was present in the provided workspace files.

English summary: the challenge provides a stripped ELF service reachable at nc challs.umdctf.io 30301. It asks for three positive integers a, b, and c with a > b > c, performs a large amount of high-precision interval-style arithmetic, and prints the flag only if the final comparison result is negative.

Challenge summary

This solve ended up being a reverse-engineering problem that turned into a math problem.

The binary did not hide the success path: main eventually opens flag.txt. The hard part was understanding what the huge wall of helper calls was actually computing. After dynamic reversing, the repeated wrapper at 0xfd60 and the final compare helper at 0xfe70 were enough to model the computation, collapse the first decisive scalar component into a rational function, and turn the acceptance condition into finding a rational point on a cubic. A birational map to an elliptic curve produced a point in the valid region x > 1, 0 < y < 1, which converted back into the exact winning integer triple.

Recon

Basic triage showed the expected hardened Linux target:

  • ELF64 PIE
  • NX enabled
  • stack canary present
  • Full RELRO

Runtime behavior was simple on the surface:

  1. print enter positive integers a,b,c; one per line:
  2. read three decimal strings
  3. require all three to be positive integers
  4. require a > b > c
  5. run a very long arithmetic pipeline
  6. on success, open flag.txt

From the disassembly, the important control points were:

  • repeated arithmetic helper around 0xfd60
  • final comparison helper around 0xfe70
  • call site in main near 0x491b

That was already enough to focus the work. This was not a memory-corruption task and not a parser bug. The whole challenge was about reconstructing the validator.

Reverse engineering

Input handling and object layout

The program reads the three integers as strings, pads them into fixed decimal buffers, and feeds them into a family of helpers that initialize and manipulate 64-byte objects. Those objects behaved like MPFR/Arb-style high-precision interval or ball values rather than plain integers or doubles.

The main hints were:

  • object sizes stayed constant at 64 bytes
  • helpers repeatedly normalized and reordered endpoint-like fields
  • the program sometimes refined uncertain comparisons instead of deciding immediately
  • the arithmetic layer accepted enormous decimal integers without overflow concerns

So the right abstraction was: the program parses a, b, and c into interval-like high-precision numbers and then composes them through many wrappers.

Recovering the repeated wrapper at 0xfd60

The large repeated helper at 0xfd60 looked intimidating statically because of all the interval bookkeeping. The useful breakthrough came from dynamic tracing and treating it as a black-box monotone transform.

Across many traced calls, the wrapper consistently matched a high-level combine of the form:

F(x, y) = exp(x) - log(y)

That was not guessed from thin air. The interval growth, monotonicity, and repeated composition patterns all lined up with applying transcendental functions to interval inputs and then combining the resulting endpoints conservatively.

Once that model was in place, the giant validation chain became understandable: it was building nested expressions over a, b, and c using interval-safe versions of exp, log, and related arithmetic.

Understanding the final compare at 0xfe70

The final gate was the helper at 0xfe70.

Its behavior was roughly:

  1. take two 64-byte interval-like objects
  2. normalize or sort endpoint fields
  3. if the current enclosure is too loose, call a refinement helper at 0x110b0
  4. return a sign-like comparison result

The key acceptance fact was discovered at the main call site near 0x491b:

  • the program accepts only when the final compare result is negative

That meant I did not need to reconstruct every single late-stage field equally. I needed the decisive scalar quantity driving the sign.

Key dead ends

Several approaches looked promising at first and wasted time:

  • Direct brute force: impossible once it became clear the accepted values were astronomically large.
  • Treating everything as ordinary floating-point math: this misses why the compare helper sometimes refines instead of deciding.
  • Trying to reverse every helper in full detail before simplifying the expression: too slow and unnecessary.
  • Looking for a parser or integer-overflow bug in the decimal input path: there was none; the validator was intentionally math-heavy.

The successful path was to recover the semantics of the recurring wrappers, identify the exact final sign test, and then simplify the decisive expression algebraically.

Mathematical derivation

After unwinding the relevant wrappers, the first scalar component of the final left-hand object simplified to:

lhs = N(a,b,c) / D(a,b,c)

with

N = -8a^3 - 7a^2b + 19a^2c + 7ab^2 + abc - 10ac^2 - 8b^2c + 8bc^2 - c^3

and

D = (a-c)(2a-b)(a+b-c)

Under the required input ordering a > b > c > 0, every factor in D is positive:

  • a - c > 0
  • 2a - b > 0
  • a + b - c > 0

So the sign of lhs is controlled entirely by the sign of N(a,b,c).

The exact winning condition was found by forcing:

N(a,b,c) = 0

This places the solution on a rational cubic. In practice, hitting the numerator exactly at zero was ideal because it parked the interval comparison on the acceptance boundary in the correct direction after the binary's refinement logic.

Normalizing to a cubic

Set:

x = a / b y = c / b

Then N = 0 becomes the affine cubic:

-8x^3 - 7x^2 + 19x^2 y + 7x + x y - 10x y^2 - 8y + 8y^2 - y^3 = 0

The input constraints translate to the region:

x > 1 0 < y < 1

At this stage the problem was cleanly separated from the reverse engineering: find a rational point on this cubic lying inside that region.

Solving the cubic via an elliptic-curve transform

The cubic has the rational point:

(-7, -4)

That is enough to birationally transform the cubic into an elliptic curve. After mapping to Weierstrass form, I used group-law search over rational points and then mapped candidate points back to the original (x, y) model.

Most rational points are useless because they land outside the required inequalities. The winning point was the one that satisfied:

  • x > 1
  • 0 < y < 1

Converting that rational point back from (x, y) to integer inputs using x = a/b and y = c/b yielded the exact triple below.

Exact winning input triple

a = 20260869859883222379931520298326390700152988332214525711323500132179943287700005601210288797153868533207131302477269470450828233936557 b = 19042526617180316524139256060457587477079898033904404413796747301621619442196095529358289579194164508926431543186710461288793130962534 c = 16792202595167632657252829598515092665938697948983181195334779924033054964579874761568657321835642556483381729386998074921169204991087

This is the exact winning input triple. It satisfies a > b > c > 0, lies on the rational cubic N = 0, and passes both local and remote validation.

Final solve method

The practical solve workflow was:

  1. reverse the input handling and locate the success path opening flag.txt
  2. identify the repeated interval wrapper at 0xfd60
  3. model that wrapper dynamically as a monotone transcendental combine F(x, y) = exp(x) - log(y)
  4. recover the semantics of the final compare at 0xfe70, including its refinement behavior through 0x110b0
  5. algebraically simplify the decisive scalar component to N / D
  6. use the positivity of D in the valid region to reduce the problem to N = 0
  7. normalize to the cubic in x = a/b, y = c/b
  8. map the cubic to an elliptic curve, search rational points, and convert back to integers
  9. send the resulting triple to the service

Solve script

The script below submits the exact winning triple locally or remotely.

#!/usr/bin/env python3 from pwn import process, remote A = "20260869859883222379931520298326390700152988332214525711323500132179943287700005601210288797153868533207131302477269470450828233936557" B = "19042526617180316524139256060457587477079898033904404413796747301621619442196095529358289579194164508926431543186710461288793130962534" C = "16792202595167632657252829598515092665938697948983181195334779924033054964579874761568657321835642556483381729386998074921169204991087" def solve(io): io.sendlineafter(b"one per line:\n", A.encode()) io.sendline(B.encode()) io.sendline(C.encode()) print(io.recvall(timeout=5).decode(errors="replace")) if __name__ == "__main__": # Local: # io = process("./main") # Remote: io = remote("challs.umdctf.io", 30301) solve(io)

If you want a tiny verification-only snippet for the algebraic condition, this checks that the numerator is exactly zero:

#!/usr/bin/env python3 A = 20260869859883222379931520298326390700152988332214525711323500132179943287700005601210288797153868533207131302477269470450828233936557 B = 19042526617180316524139256060457587477079898033904404413796747301621619442196095529358289579194164508926431543186710461288793130962534 C = 16792202595167632657252829598515092665938697948983181195334779924033054964579874761568657321835642556483381729386998074921169204991087 N = ( -8*A**3 - 7*A**2*B + 19*A**2*C + 7*A*B**2 + A*B*C - 10*A*C**2 - 8*B**2*C + 8*B*C**2 - C**3 ) print(N)

That prints 0.

Verification

Local execution with the triple above prints the flag.

Remote execution returns:

UMDCTF{ok_i_got_the_nuclear_codes_now_where_do_i_enter_them????}

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups