$ cat writeup.md…
$ cat writeup.md…
umasscybersec
Task: a not-stripped ELF64 license checker was intentionally damaged by bit flips, leaving several obviously broken arithmetic and crypto-looking routines. Solution: ignore the noisy verifier, recover the plaintext directly by XORing the stored FLAG and EXPECTED blobs from .data, and use the embedded NUL to explain the printed flag boundary.
Organizer description was not preserved in the local task files.
English summary: the challenge provided a corrupted ELF64 license checker with enough symbols left intact to inspect its globals and helper routines. The intended verification path was damaged, but the plaintext flag could still be recovered directly from the data section.
The binary was not stripped, which immediately exposed useful globals: LICENSE_KEY, EXPECTED, FLAG, and SBOX, plus functions such as rotate, decrypt_flag, hash, and verify. Several instructions looked wrong in a way that strongly suggested bit-flip corruption rather than ordinary obfuscation.
The most suspicious examples were:
rotate used (b * 8) | (b >> 6) instead of a normal 3-bit rotate-left like (b << 3) | (b >> 5).decrypt_flag used bitwise OR where XOR made much more sense for symmetric recovery.hash iterated for 0xBEEEEE rounds, which looked deliberately corrupted or at least untrustworthy.At that point, fully repairing the verification path was unnecessary. The key observation was that both EXPECTED and FLAG were stored in .data, and XORing them immediately produced readable output:
UMASS{__p4tche5_0n_p4tche$__#}\x00\xee
The embedded NUL explains why the program would print only UMASS{__p4tche5_0n_p4tche$__#} with %s. The remaining byte 0xee is just trailing garbage after the string terminator. The embedded LICENSE_KEY string !_batman-robin-alfred_((67||67)) is likely the intended thematic license value, but recovering the flag did not require repairing the broken hash pipeline enough to validate it cleanly.
EXPECTED and FLAG.rotate and decrypt_flag.#!/usr/bin/env python3 EXPECTED_HEX = "3b54751a2406af05778047c5e483d348cb8730de1a9145ab15c79b2204022bee" FLAG_HEX = "6e193449777df05a07b433a68ce6e617fbe96fae2ee526c370e3c47d277f2b00" def xor_bytes(a: bytes, b: bytes) -> bytes: return bytes(x ^ y for x, y in zip(a, b)) def main(): expected = bytes.fromhex(EXPECTED_HEX) flag_blob = bytes.fromhex(FLAG_HEX) plaintext = xor_bytes(expected, flag_blob) print("[+] Raw plaintext bytes:", plaintext) print("[+] Printed flag:", plaintext.split(b"\x00", 1)[0].decode()) if __name__ == "__main__": main()
Running the script yields the raw bytes UMASS{__p4tche5_0n_p4tche$__#}\x00\xee, so the visible flag is the prefix before the NUL byte.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.