Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
ReverseMedium

Rusty Pool Party

CTF writeup for Rusty Pool Party from heroCTF

//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 to TpWorkerFactory via XOR) and msedge.exe suggested process injection or interaction.
  • Entropy: High entropy in the .rdata section indicated encrypted data blobs.

>Step 2: Static Analysis & Decryption Logic

Using objdump and static analysis, we identified the core decryption mechanism:

  1. XOR Loops: several simple XOR loops decrypted API names and strings.
  2. RC4 Encryption: A standard RC4 Key Scheduling Algorithm (KSA) was identified at 0x140004ec5.
  3. Encrypted Blobs: We found four distinct encrypted blobs in the .rdata section. 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, Size 0xc80
  • Blob 2: Offset 0x7325, Size 0x1e30
  • Blob 3: Offset 0x9165, Size 0x18f0
  • Blob 4: Offset 0xaa65, Size 0x1ce0

We successfully decrypted three of these blobs, revealing parts of the flag (T_0T_2T_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:

BlobOffsetSizeKey Offset
Blob 10x66950xc800x7315
Blob 20x73250x1e300x9155
Blob 30x91650x1ce00xae45
Blob 40xae550x18f00xc745

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.

python
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 SegmentoL_P4r7Y} (forming Pool_Party)

>Conclusion

The final flag is: Hero{y0u_700_YoU_l1K3_7h4t_KiNd_0F_P0oL_P4r7Y}