ReversingHidden Investigations CTF 2026
Night Batch
night-batch is a GnuCOBOL 3.2 program (cobc output, libcob and GMP statically linked, stripped). It needs glibc 2.38+, so I ran it in ubuntu:24.04.
What this one teaches
- GnuCOBOL-compiled binaries use cob_intr_binop/mod/cmp_llint for arithmetic checks — tracing these library calls under gdb extracts the constraint system without reading COBOL.
- A system of modular equations with a few nonlinear terms (digit products) is best solved by DFS with early pruning, not SAT or Z3.
- NOP-patching conditional jumps to the reject path lets you trace the full check sequence in one run, even when the input is wrong.
Task description
* ----------------------------------------------------------------- * * OPS NOTE -- taped to the 3270 before the floor was cleared * * ----------------------------------------------------------------- * * NIGHTBATCH acct-validation step. the load module is all we kept; * * the COPYLIB and source rode out with the frame. it still runs: * * * * ./night-batch <account-key> * * * * a good key clears the step. everything else bounces. the key was * * on a card by the console -- card's gone. recover it from the module.* * ----------------------------------------------------------------- *Linux x86-64 ELF.
Key: 481702596318740925
How it works
main(0x400940) callscob_init, then the COBOL program at 0x400a70.- The key goes into a 40-byte field. It must be 18 characters long and
IS NUMERIC(cob_check_numeric, 0x404bc0). Each digit is moved into its own packed-decimal fieldD1..D18at 0x460100. - Then 23 checks run in sequence. Each one is built from
cob_intr_binop(0x41df30), thencob_intr_mod(0x41e530), thencob_cmp_llint(0x413920). The first failed check jumps to 0x400b77 and prints “account rejected”. - Success path (0x402f40):
H0 = SHA256(key). Keystream blocks areK_i = SHA256(H0 || be32(i)). The 42-byte ciphertext at 0x44d0c0 is XORed with the keystream. The result is accepted only if its SHA-256 equals an embedded constant, and is then printed. Otherwise the program prints “account rejected”, whose text is XOR 0x5a obfuscated at 0x44d0a0.
Recovering the key
- I NOP’d every
jne 400b77in a copy of the binary. I then tracedbinop,modandcmp_llintunder gdb with a Python script. This logs every equation: weights, digit indices, modulus and target. - The 23 equations are all of the form
sum(w * Dx) mod M == c. Two of them use a digit product (D2*D11 = 8andD2*D10 = 4, both mod 10). The last one is mod 100:9*D17 + 2*D16 + 5*D11 + 5*D7 + 4*D4 ≡ 94 (mod 100). The other 22 are mod 10. Examples:D4+D5+D9 ≡ 3,D1+D5+D9 ≡ 0,D1+D12+D18 ≡ 7. - A DFS over the digits (ordered so equations are checked as early as possible) finds one solution: 481702596318740925.
- Check:
./night-batch 481702596318740925printshiCTF{c0b0l_p4ck3d_d3c1mal_n1ghtly_b4tch!}.
Submission
The API returned {"kind":"goodFlag","message":"The flag is correct."}.
The flagReveal
hiCTF{c0b0l_p4ck3d_d3c1mal_n1ghtly_b4tch!}This CTF was possibly solved, and the writeup most likely written by an LLM. Apologies for inconsistencies.