//Rusty Pool Party
>Introduction
"Rusty Pool Party" was a reverse engineering challenge that involved analyzing a Windows PE executable. The binary employed a custom process injection technique (hinted at by "Pool Party" and strings like TpWorkerFactory) and protected its secrets using RC4 encryption. The challenge had a unique twist: the binary served by the remote instance was different from the one initially provided, requiring dynamic adaptation.
>Step 1: Initial Reconnaissance
We started with the provided binary rusty_pool_party.exe.
- File Type: PE32+ executable (console) x86-64.
- Strings: Suspicious strings like
kOhPMTZMy^\KPMFUnknownZw(which decrypted toTpWorkerFactoryvia XOR) andmsedge.exesuggested process injection or interaction. - Entropy: High entropy in the
.rdatasection indicated encrypted data blobs.
>Step 2: Static Analysis & Decryption Logic
Using objdump and static analysis, we identified the core decryption mechanism:
- XOR Loops: several simple XOR loops decrypted API names and strings.
- RC4 Encryption: A standard RC4 Key Scheduling Algorithm (KSA) was identified at
0x140004ec5. - Encrypted Blobs: We found four distinct encrypted blobs in the
.rdatasection. Each blob was immediately followed by its 16-byte RC4 key.
The "Old" Binary Structure
In the initial binary, we identified:
- Blob 1: Offset
0x6695, Size0xc80 - Blob 2: Offset
0x7325, Size0x1e30 - Blob 3: Offset
0x9165, Size0x18f0 - Blob 4: Offset
0xaa65, Size0x1ce0
We successfully decrypted three of these blobs, revealing parts of the flag (T_0, T_2, T_3). However, blob4 (containing T_1) refused to yield a coherent string, despite our best brute-forcing efforts.
>Step 3: The Twist - A New Binary
After struggling with blob4, we realized the remote environment might be serving a different binary. We downloaded the executable from the running instance (dyn15.heroctf.fr) and compared it to our local copy.
- MD5 Mismatch: The hashes were different.
- Structure Change: While the code logic was similar, the offsets and sizes of the encrypted blobs had changed. specifically, the order and sizes of the last two blobs were swapped.
>Step 4: Re-Analysis & Adaptation
We re-analyzed the new binary and mapped the correct offsets:
| Blob | Offset | Size | Key Offset |
|---|---|---|---|
| Blob 1 | 0x6695 | 0xc80 | 0x7315 |
| Blob 2 | 0x7325 | 0x1e30 | 0x9155 |
| Blob 3 | 0x9165 | 0x1ce0 | 0xae45 |
| Blob 4 | 0xae55 | 0x18f0 | 0xc745 |
Note: In the new binary, Blob 3 became the larger one (0x1ce0), and Blob 4 became the smaller one (0x18f0).
>Step 5: Final Decryption
With the correct offsets, we wrote a script to decrypt all four blobs.
import sys
def rc4(key, data):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for b in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(b ^ S[(S[i] + S[j]) % 256])
return res
blobs = [
{"name": "blob1_new.dec", "offset": 0x6695, "size": 0xc80, "key_offset": 0x7315},
{"name": "blob2_new.dec", "offset": 0x7325, "size": 0x1e30, "key_offset": 0x9155},
{"name": "blob3_new.dec", "offset": 0x9165, "size": 0x1ce0, "key_offset": 0xae45},
{"name": "blob4_new.dec", "offset": 0xae55, "size": 0x18f0, "key_offset": 0xc745},
]
with open("rusty_pool_party.exe", "rb") as f:
data = f.read()
for b in blobs:
enc_data = data[b["offset"] : b["offset"] + b["size"]]
key = data[b["key_offset"] : b["key_offset"] + 16]
dec_data = rc4(key, enc_data)
with open(b["name"], "wb") as f_out:
f_out.write(dec_data)
print(f"Decrypted {b['name']}")
>Step 6: Flag Reconstruction
The decrypted blobs contained the flag segments:
- Blob 1 (T_0):
Hero{y0u_700_ - Blob 4 (T_1):
YoU_l1K3_7h4 - Blob 3 (T_2):
t_KiNd_0F_P0 - Blob 2 (T_3):
oL_and_P4r7Y}
The Final Puzzle Piece
Simply concatenating them gave Hero{y0u_700_YoU_l1K3_7h4t_KiNd_0F_P0oL__P4r7Y}, which was incorrect. We disassembled the decrypted blob2 to see how the string was constructed in memory.
movabs $0x5f4c6f203a335f54,%rdx ; "T_3: oL_"
...
movabs $0x7d59377234505f,%rax ; "_P4r7Y}"
The assembly revealed that the strings were written to memory in a way that they overlapped. The trailing _ of oL_ and the leading _ of _P4r7Y} shared the same byte, meaning the final string contained only one underscore at that position.
Corrected Segment: oL_P4r7Y} (forming Pool_Party)
>Conclusion
The final flag is: Hero{y0u_700_YoU_l1K3_7h4t_KiNd_0F_P0oL_P4r7Y}