Overview
| Field | Value |
|---|---|
| Category | Intermediate |
| Author | toasterbirb |
| Binary | ELF 64-bit x86-64 PIE, C++, not stripped |
| Description | “No matter where you go, everybody’s connected.” |
| Flag | CMO{secret_code_v9hcdkd2} |
Recon
The binary is a 142KB C++ program simulating a network of PCs, switches, and routers. Key imports: rand, srand, time, nanosleep, sscanf, getline, toupper, memcmp, strtol, std::to_chars, plus C++ STL (strings, maps/rb-trees, iostream, deques).
Custom functions include str_to_ip, ip_to_str, adler_32, fletcher_16, is_same_subnet, and 9 lambda callbacks for each PC.
Network Topology
Switch 1 (38.15.199.0/24) Switch 2 (100.25.26.0/24)
PC 38.15.199.42 [#1: printer] PC 100.25.26.10 [#4: orchestrator]
PC 38.15.199.41 [#2: flag builder] PC 100.25.26.11 [#5: spammer]
PC 38.15.199.40 [#3: OK responder] PC 100.25.26.15 [#6: validator]
| |
Router 1 (38.15.199.1) Router 3 (100.25.26.1)
Router 1 (42.20.102.1) Router 3 (85.32.15.2)
| |
Router 2 (42.20.102.2) -------- Router 2 (85.32.15.1)
Router 2 (185.23.59.2) Router 3 (64.16.15.1)
| |
Router 4 (185.23.59.1) Switch 3 (64.14.3.0/24)
Router 4 (83.48.92.1) PC 64.14.3.25 [#7: length counter]
| PC 64.14.3.29 [#8: checksum calc]
Switch 4 (83.48.92.0/24)
PC 83.48.92.5 [no handler]
PC 83.48.92.8 [#9: XOR service]
Program Flow
main (0x696e)
- Seeds RNG with
time(0) - Constructs the entire network: 4 switches, 3 routers, 10 PCs with routing tables
- Assigns a lambda callback to each PC (except 83.48.92.5 which has none)
- Prompts user for
what(message) andwhere(destination IP) - Sends a packet from PC 38.15.199.42 with:
{flags=0, src=38.15.199.42, dst=user_ip, ttl=32, payload=user_msg}
Lambda Callbacks
| # | PC IP | Address | Behavior |
|---|---|---|---|
| #1 | 38.15.199.42 | 0x35c0 |
Prints received: <payload> – the output display |
| #2 | 38.15.199.41 | 0x632d |
Constructs flag from payload. Rejects packets from 38.15.199.42 |
| #3 | 38.15.199.40 | 0x3b35 |
Responds with “OK” |
| #4 | 100.25.26.10 | 0x55a2 |
Orchestrator – multi-step validation pipeline |
| #5 | 100.25.26.11 | 0x5261 |
Sends 4096 spam packets then forwards |
| #6 | 100.25.26.15 | 0x4e8c |
Proxies to 83.48.92.8, validates byte properties |
| #7 | 64.14.3.25 | 0x4798 |
Proxies to 83.48.92.8, returns payload length as string |
| #8 | 64.14.3.29 | 0x3f24 |
Proxies to 83.48.92.8, returns checksum as string |
| #9 | 83.48.92.8 | 0x3b95 |
XOR service: XORs each byte with 0x42. Returns “…” if any byte is ‘B’ |
Orchestrator Pipeline (#4 at 0x55a2)
The orchestrator on PC 100.25.26.10 is the key. When it receives a packet from 38.15.199.42:
- Prefix check: Payload must start with
msg_. Strips the prefix -> message M. - Empty check: M must not be empty.
- Step 1: Forwards M to 83.48.92.8 -> #9 XORs with 0x42 -> stores result as
xored - Step 2: Forwards
xoredto 64.14.3.25 -> #7 forwards to #9 (double XOR = original M) -> #7 counts length -> returns length string -> stored aslen_str - Step 3: Forwards
xoredto 64.14.3.29 -> #8 forwards to #9 (double XOR = M) -> #8 computes checksums -> returns checksum string -> stored ascksum_str - Step 4: Forwards
xoredto 100.25.26.15 -> #6 forwards to #9 (double XOR = M) -> #6 validates byte properties -> returns “1” or “0” -> stored asvalid_str
Final check (verified from disassembly at 0x5E96):
|
|
If all three pass, M is forwarded to 38.15.199.41 (the flag builder). Otherwise, “I don’t want to talk to you” is sent back.
XOR Service (#9 at 0x3b95)
Iterates through each byte of the payload:
- If byte == 0x42 (‘B’): stops and responds with “…” (three dots)
- Otherwise: XORs byte with 0x42
Returns the XOR’d payload to the sender.
Byte Validator (#6 at 0x4e8c)
After receiving the double-XOR’d (= original) response from #9:
- Check 1: All bytes must have bit 0 clear (all even)
- Check 2: All bytes must be in range [33, 126] (printable)
- Returns “1” if both pass, “0” otherwise
Checksum Calculator (#8 at 0x3f24)
After receiving the double-XOR’d (= original) response from #9, computes:
adler = adler_32(M, len)
fletch = fletcher_16(M, len)
custom = sum(M[i] << i for i = 0..len-1)
is_pal = (M is a palindrome)
if is_pal:
result = custom ^ fletch ^ adler
else:
result = custom
Returns result as a decimal string.
Flag Builder (#2 at 0x632d)
When receiving from any IP except 38.15.199.42:
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789_"
flag = "CMO{secret_code_"
for i in range(len(M)):
flag += alphabet[(i + M[i]) % 37]
flag += "}"
Sends the flag back to 38.15.199.42, where #1 prints it.
Constraints Summary
The message M (8 bytes) must satisfy:
- Length: exactly 8
- No ‘B’: M cannot contain byte 0x42 (would break XOR service)
- Even bytes: all bytes must have bit 0 clear
- Printable: all bytes in [34, 126]
- Palindrome: M must read the same forwards and backwards (otherwise the checksum formula doesn’t include adler/fletcher and can’t reach the target)
- Checksum:
custom_hash ^ fletcher_16 ^ adler_32 = 0x6022E46
Solve
Since M is an 8-byte palindrome with 4 independent bytes, each from a set of 46 valid values (even, [34,126], not 66), the search space is 46^4 ~ 4.5M – trivially brute-forceable.
|
|
Unique solution: M = [58, 34, 42, 36, 36, 42, 34, 58] = :"*$$*":
custom_hash= 12102fletcher_16= 85 (0x55)adler_32= 0x0602015512102 ^ 85 ^ 0x06020155=0x06022E46
To run the program: input msg_:"*$$*": as “what” and 100.25.26.10 as “where”.
Lessons Learned
- Complex multi-stage network simulations can be reverse engineered by mapping each node’s callback behavior individually, then tracing the full message flow.
- The palindrome requirement was critical: without it, the custom hash alone (max ~32K) could never reach the target value (~100M). The XOR with adler_32 (a 32-bit value) was needed to reach that range.
- Callback offset pattern: each
net_pcstores its function pointer at offset +0x268 from the structure start on the stack.
Flag
CMO{secret_code_v9hcdkd2}