Intergalactic - Beacon v0
Summary
The binary is a stripped 64-bit PIE ELF that always prints 72 bytes, but that output is not the flag directly.
There are two layers:
mainbuilds a 72-byte beacon buffer.- If a success bit is not set, the buffer is transformed into a decoy before being written.
The intended solve path for the fixed challenge is to recover the internal success-path plaintext generated by the state machine inside sub_2d90 and identify the seed that makes the internal hash equal the target constant.
Final flag:
SK-CERT{pl4y_w1th_f33db4ck_l00p5_d1e_w1th_0b5cur1ty}Initial Recon
The workspace only contained one binary:
./beaconv0Basic checks showed:
- stripped PIE executable
- dynamically linked
- imports included
getenv,getpid,syscall,getrandom,clock_gettime,strstr,write
Running it normally produced 72 bytes of binary output, not text.
Main Function Behavior
The top-level flow is:
- collect several anti-analysis signals
- derive a 16-bit seed from
ptrace - derive another value from
getrandomorclock_gettime - initialize a small context
- run a larger state machine that fills a 72-byte buffer
- if the state machine marked success and no anti-analysis checks fired, write the raw 72-byte buffer
- otherwise mutate the buffer and write a decoy
The anti-analysis checks were:
ptrace(PTRACE_TRACEME)-style syscall behavior- environment variables:
LD_PRELOADLD_AUDITDYLD_INSERT_LIBRARIES
- parent-vs-self pid consistency
/proc/self/statusparsing forTracerPid:
Under a debugger, the success path is suppressed even if the internal buffer is correct.
Key Observation
The real gate is in sub_2d90.
At the end of that function:
- 72 plaintext bytes have been produced internally
- an internal rolling hash at offset
+0x100is compared against0xd43a15c5 - if it matches, offset
+0x114is set to1
That success bit is what main later checks.
So the problem reduces to:
- emulate
sub_2d90 - recover the plaintext bytes passed through the internal output routine
- brute-force the 16-bit seed
- stop when the internal hash matches
0xd43a15c5
Why I Used Unicorn
I initially reversed a large part of the state machine manually, but sub_2d90 is dense and heavily mixed.
The cleaner approach was:
- use the real machine code
- relocate the PIE in memory
- apply ELF
R_X86_64_RELATIVErelocations - emulate only the target function with Unicorn
- hook the internal output helper at
0x401c10
That helper receives each plaintext byte in RDX. By intercepting it, I could collect the true 72-byte success-path plaintext without reproducing the entire algorithm in Python.
Solver Approach
The included solver is:
What it does:
- maps the binary into Unicorn memory
- applies relative relocations
- sets up synthetic stack, FS canary area, and scratch regions
- emulates
sub_2d90 - hooks
0x401c10to capture plaintext bytes - brute-forces the 16-bit seed space
- checks whether the state-machine success bit was set
The winning seed was:
0x5eedThe solver output for that seed is:
VOID::SECTOR-7::SK-CERT{pl4y_w1th_f33db4ck_l00p5_d1e_w1th_0b5cur1ty}::OKReproduction
Run:
python3 solve_beacon.py 0x5ee0 0x5ef0Expected output:
seed=0x5eed
hash=0xd43a15c5
out=72
VOID::SECTOR-7::SK-CERT{pl4y_w1th_f33db4ck_l00p5_d1e_w1th_0b5cur1ty}::OKFlag
SK-CERT{pl4y_w1th_f33db4ck_l00p5_d1e_w1th_0b5cur1ty}