Весёлый EXE (Funny EXE)
Весёлый EXE (Funny EXE)
Platform: hackerlab | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Windows | Author: XESXOR | Date: 2026-06-02 | Status: Solved Techniques: brute_force_per_byte, byte_transform_inversion, live_server_keystream_extraction, network_protocol_replay, post_main_crt_hijack, xref_following
Summary
Task: a MinGW PE32+ that trolls with meme wallpapers/sounds; the real flag check runs from the CRT startup after main returns, talking a custom TCP protocol to a hardcoded C2. Solution: locate the hidden post-main function via xrefs, replay the PASS protocol to a live server to obtain the keystream + encrypted flag, then invert the self-inverting per-byte cipher by brute-forcing printable chars.
Весёлый EXE (Funny EXE) — hackerlab
Description
Смотри, что сделал мой друг. Правда некоторые картинки я не понял. Точно, он туда "вшил" что-то связанное с флагом. Не помню, что только
English translation: "Look what my friend made. True, I didn't understand some of the pictures. Right, he 'embedded' something flag-related in there. Just don't remember what."
We are given a single Windows executable good_exe.exe (PE32+ x86-64, MinGW-W64 GCC 8.1.0, 25600 bytes, MD5 c75e67da80f26dd0cb1006001cce5e7c). The goal is to recover a CODEBY{...} flag. The "pictures" mentioned in the description (desktop wallpapers the program sets, plus an embedded Unicode ASCII-art dog) are intentional decoys — they never participate in deriving the flag.
Analysis
Initial recon
good_exe.exe is a console PE with no resources and no flag string stored anywhere in the binary. Strings of interest are obfuscated. Two distinct network code paths reference the same hardcoded endpoint:
- IP
62.173.140.174— ASCII string in.rdata(file offset0x4424/ VA0x406024), referenced byinet_ptonin both code paths. - Port
14100—mov ecx, 0x3714thenhtons(0x3714)→ TCP 14100 (constants at0x401a7cand0x402042).
main() is a troll branch
main (0x40216f) prints:
This EXE may change something on your PC. Are you sure you want to run it? (y/n)
If the user answers y, it loops 100 times calling sub_401A11 (the GET path): connect to the server, send "GET", receive two files, save them hidden (SetFileAttributesA HIDDEN), set one as the desktop wallpaper (SystemParametersInfoA SPI_SETDESKWALLPAPER) and play the other (PlaySoundA). This whole branch is pure misdirection — it spams meme wallpapers and sounds, matching the "pictures" in the task description.
The real logic runs AFTER main returns (anti-analysis)
The key insight: the flag check is not in main. After main returns, the MinGW CRT startup (around 0x4013db) checks a global and calls sub_401FD7, the PASS path. To find it in IDA:
- Click
main→ pressX(xrefs) → follow the call coming from the CRT startup. - Inside that "standard system function" there is a call to a function that shouldn't normally be there —
sub_401FD7.
sub_401FD7 (PASS path):
WSAStartup,socket,connectto62.173.140.174:14100.- The command string
"PASS"is stored obfuscated at0x404e20as bytes8f 80 92 92and decoded at runtime by subtracting0x3ffrom each byte (8f-3f=50='P', etc.). - Sends
"PASS", waits for a reply beginning with"OK"(viasub_4015F0), then callssub_401B84.
The check / cipher (sub_401B84)
recv(sock, buf, 100, 0); // 100 bytes; only first 68 used
Str = buf[0x00 .. 0x22]; // encrypted flag (0x22 = 34 bytes)
key = buf[0x22 .. 0x44]; // keystream (0x22 = 34 bytes)
// remaining 32 bytes ignored
GetUserNameA(Buffer, &pcbBuffer); // Windows username
It checks strlen(Str) == strlen(username), transforms the username in place, then memcmp against Str. On match it shows MessageBoxA("Success! This is your flag ! :)") — that success string is itself hardcoded and decoded with -0x3f; it is not the flag.
Per-byte transform (v8 = key, v19 = previous output byte, chaining):
c read-only • copy allowedBuffer[i] ^= v8[i]; Buffer[i] += v8[i]; Buffer[i] -= v8[i]; Buffer[i] ^= v8[i]; Buffer[i] -= v8[i]; Buffer[i] ^= v8[i]; Buffer[i] ^= v19; // chain with previous output v19 = Buffer[i];
The flag is the username that, after this transform with the server-supplied key, equals the server-supplied Str. Equivalently, brute-force each printable char j (0x20..0x7f) through the same transform and compare to Str[i]; the matching j is the plaintext flag byte. The chaining variable propagates as last = Str[i] after each position.
Why offline / the official writeup don't suffice
The full 0x22-byte key and encrypted flag are not embedded in the EXE — they only arrive from the live server in the recv(...,100) call. The official platform writeup ships a C decryptor whose key[] array is truncated to 13 bytes and correct[] to 17 bytes (screenshots cut off at the right margin). Compiling it verbatim prints only CODEBY{n2tw0rK_h because for i>=13 it reads key[] out of bounds (UB). A reliable solve requires talking to the server.
Solution
- TCP-connect to
62.173.140.174:14100. - Send
b"PASS". - Receive
b"OK". - Receive the 68-byte payload:
s1(encrypted flag, 0x22 bytes) =3bb496af93d2a9258fe5a282f2b9e68ec26c2b7435597b244c6139bce383f6ba89f4key(0x22 bytes) =beef1337abcdeb75ccaf9988619352ec1bc3c3c41105130a880e0ee15e231095a8a4(note thebeef1337prefix)
- Invert the transform by brute-forcing the printable char at each position, carrying the chaining value
last = s1[i].
Working solver
python read-only • copy allowedimport socket HOST, PORT = '62.173.140.174', 14100 def step(j, k, last): t = j & 0xff t ^= k; t = (t + k) & 0xff; t = (t - k) & 0xff t ^= k; t = (t - k) & 0xff; t ^= k t ^= last return t # 1) pull encrypted flag (s1) + key from the live server via PASS protocol s = socket.socket(); s.settimeout(8); s.connect((HOST, PORT)) s.sendall(b'PASS') assert s.recv(10)[:2] == b'OK' data = b'' while len(data) < 100: chunk = s.recv(100 - len(data)) if not chunk: break data += chunk s.close() s1, key = data[:0x22], data[0x22:0x44] # 2) invert the byte-wise transform: brute printable char at each position last = 0 flag = '' for i in range(len(s1)): for j in range(0x20, 0x7f): if step(j, key[i], last) == s1[i]: flag += chr(j) last = s1[i] break print(flag) # CODEBY{REDACTED}
Indicators of Compromise
- C2-like host:
62.173.140.174:14100— custom TCP, commandsGETandPASS, expects reply starting withOK. - Server-supplied key prefix
beef1337in the 0x22-byte keystream. - Sets desktop wallpaper (
SystemParametersInfoA SPI_SETDESKWALLPAPER) + plays sound (PlaySoundA), writes hidden files (SetFileAttributesAHIDDEN) — prank/dropper behavior. - Binary:
good_exe.exe, MD5c75e67da80f26dd0cb1006001cce5e7c, PE32+ x86-64, MinGW-W64 GCC 8.1.0, image base0x400000.