# REVERSE

## MouseTrap

### Description

APT27 targeted us and something was leaked can you help and identify what was leaked ?

### Goal

Decrypt the PCAP traffic to retrieve the flag.

### **Challenge**

{% file src="/files/UscEJCLRQQvEqmdDHBMW" %}

### **Static Analysis (IDA)**

Load `MouseTrap.exe` into IDA.

#### **Main function**

<figure><img src="/files/hMtSd96n8n3QpyJ3Nr6c" alt=""><figcaption></figcaption></figure>

#### **Anti-debugging**

* The calls `CreateDesktopA("LuckyMouse")` and `SwitchDesktop` move the process to a private desktop, which blanks your screen and hides any UI on a black/empty desktop.
* The call `NtSetInformationThread(..., 17)`, an anti-debug tactic that blocks first-chance exceptions and debugging events from reaching WinDbg/IDA.

To debug and analyze, I `NOP`’d these anti-debug sections.

#### **Patched version**

<figure><img src="/files/VgEIQcfKrOSufruripoE" alt=""><figcaption></figcaption></figure>

### **Dynamic Analysis (Procmon)**

#### **Setup**

* Configure Procmon to monitor **MouseTrap.exe**.

<figure><img src="/files/MIHl6l7tolBF9VtD3XWd" alt=""><figcaption></figcaption></figure>

#### **Findings**

* Run the malware

<figure><img src="/files/58TRp7Nzb0TfkZFbaDPB" alt=""><figcaption></figcaption></figure>

The malware tries to open `C:\creds.txt`. If it doesn’t exist, it exits.

* Create **C:\creds.txt** and run again

<figure><img src="/files/jUrixCFkDl4KZEaBs5wl" alt=""><figcaption></figcaption></figure>

* Run the malware again

<figure><img src="/files/nbf628VyBCrzZ8CrU4BF" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/yA7ezVOwM4cpT4WkaUWB" alt=""><figcaption></figcaption></figure>

The malware reads the file via `ReadFileEx`, encrypts the content, and attempts to connect to `127.0.0.1:13337` to send the encrypted data (the same traffic seen in the PCAP).

### **Dynamic Analysis (IDA/WinDbg)**

#### **Catching the crypto**

* Initial setup

Set a breakpoint on `kernel32!ReadFileEx`, continue, and configure the debugger not to break on exceptions.

<figure><img src="/files/hUhUHXFMKkFUsA9OVGuC" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Nv2FoH3YpftzsUMekkNh" alt=""><figcaption></figcaption></figure>

* After resuming execution, the **ReadFileEx** breakpoint triggered.

<figure><img src="/files/JOwpTpzFVpLRsMsCfV61" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QodZKtiSHH9L17JGNLxm" alt=""><figcaption></figcaption></figure>

Based on the stack arguments, **lpBuffer** is **0x005853B0** in `.data` (`dword_5853B0`); **0x005853B0** holds the content.

* Set a hardware read breakpoint on `dword_5853B0` and continue.

<figure><img src="/files/rftaSNXAbcnsp9r1ZXiI" alt=""><figcaption></figcaption></figure>

* A read triggers inside a routine at `sub_401000` function (Encryption)

<figure><img src="/files/DCHVnpybKQI9I3BKUsJu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/4N36jNZQ2WH8wyDKeiHW" alt=""><figcaption></figcaption></figure>

### **Identifying the algorithm**

* Search the constants used by the routine

<figure><img src="/files/1UFIxEz4PICmb81KfuoR" alt=""><figcaption></figcaption></figure>

Searching these leads to [**SPC**](https://github.com/chsjiang/spc) (a tweakable Lai-Massey block cipher using a SipHash-like core). The implementation matches the public reference (SPC over 64-bit words, 128-bit key, 128-bit block, 56-bit tweak folded into a 64-bit word).&#x20;

### **Decrypt the PCAP Traffic**

* I reproduced the SPC round function in Python via **GPT 5** and used the key/tweak lifted from .data section.
* Export the TCP stream from the PCAP as hex and feed it to the script to obtain the plaintext.

<figure><img src="/files/u0ml5uZbD9yKnW1eK4hp" alt=""><figcaption></figcaption></figure>

#### **Script**

```python
# solution.py
from binascii import unhexlify, hexlify

MASK64 = (1 << 64) - 1

def rotl(x, b): return ((x << b) & MASK64) | (x >> (64 - b))

def sipround(v0, v1, v2, v3):
    v0 = (v0 + v1) & MASK64
    v1 = v0 ^ rotl(v1, 13)
    v0 = rotl(v0, 32)
    v2 = (v2 + v3) & MASK64
    v3 = v2 ^ rotl(v3, 16)
    v0 = (v0 + v3) & MASK64
    v3 = v0 ^ rotl(v3, 21)
    v2 = (v2 + v1) & MASK64
    v1 = v2 ^ rotl(v1, 17)
    v2 = rotl(v2, 32)
    return v0, v1, v2, v3

def sha4(round_i, k0, k1, tw, X):
    v0 = 0x50726f736563636f ^ k0
    v1 = 0x43686f636f6c6174 ^ k1
    v2 = 0x01f32d1f4361f48e ^ k0
    v3 = ((round_i & 0xFF) << 56) ^ tw ^ k1
    v3 ^= X
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    v0 ^= X
    v2 ^= 16
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    return (v0 ^ v1 ^ v2 ^ v3) & MASK64

def sigma(L):
    X = L & 0xffffffff
    return ((X << 32) | (((L >> 32) ^ X) & 0xffffffff)) & MASK64

def unsigma(L):
    X = (L >> 32) & 0xffffffff  # upper 32
    Y = L & 0xffffffff          # lower 32
    return (((Y ^ X) << 32) | X) & MASK64

def le64(b): return int.from_bytes(b, 'little')
def be64(x): return x.to_bytes(8, 'little')

# Keys/tweak lifted from .data segment
KEY  = bytes.fromhex("3d1790908e448599b0829e584cf2cd56")
TWEAK = bytes.fromhex("aa12de3344b4c6f8")

def decrypt_block(ct16, key=KEY, tweak=TWEAK):
    k0 = le64(key[:8]); k1 = le64(key[8:])
    tw = le64(tweak)
    L = le64(ct16[:8]); R = le64(ct16[8:])
    for i in (3, 2, 1, 0):
        L = unsigma(L)
        X = sha4(i, k0, k1, tw, L ^ R)
        L ^= X; R ^= X
    return be64(L) + be64(R)

def decrypt(hex_str, key=KEY, tweak=TWEAK):
    h = ''.join(hex_str.split()).lower()
    if len(h) % 32 != 0:
        raise ValueError("Ciphertext hex length must be a multiple of 32 (16 bytes per block).")
    data = unhexlify(h)
    out = bytearray()
    for off in range(0, len(data), 16):
        out += decrypt_block(data[off:off+16], key, tweak)
    return bytes(out)

def pretty_ascii(b):
    return ''.join(chr(x) if 32 <= x < 127 else '.' for x in b)

if __name__ == "__main__":
    ct_hex = "2f012a1e13a9a54b103424d8873cf1685e61ccc93f9dcae98e7b8f3ed0f862a66c72df99dcf517982af398a726d8d22f881635d66b8d0a5c67582f397a88f8b3131a5bab38430b60a068eaeb3638712f9376b89eb05adb31e8a8472f5060aab8ab880ed106e9d743cc34f7a593b25283bfcc23d8f03ddf79fa319ed355c85e9779ae9ee61878f0832c9b5825a664c8f922303edc220256435fdff0b650d7ea6c88f2961aa53da9f69bb7849f582c79794e5d6aa9bf2f3210013d5bb7c8a2b819dfa8a1ee0f1cf71172995895e5778796624178017ea89df5f949f7bf7a800a7a740d6070117f1742b41eb8cb5bc7f02b42a4476da7657deacf13147e1b879d40d20d0fd34c6d055aca1368b3803384c74d8108b1c58f14d77dc10b3e6d9a095aa9e13ec09f009a7c97e2cd9d883bab15de027e8ffe45552941f2f9e33e16b0f0afe3abbc865ff7c629ca794e4e1c159c53a0174a57f4f94b979e8551796a1418548916de67c13cf83400427edfcce65f41582ea54541f1dacf2a65b90607e123c2718f610e0ae2527ea40b3ad82a6692896d36b65a1e465bb7abcbaf7f44017f8d3535d4c1beeb3ed5be6a061fa3e6d9e656e910f7ed9a3080d7f044786c9a86e54ed1d87c03dde9c8b70703e6376aa65bcc02690a67cbaf1b0cbb3b944383bd87f6ab3f9aa2c29a3a2f260f844ee064ec3ed0e3c84c63979507e9ab3e7e94af9088d622aea6ca505048e859bef1138e5fa9115d1aab7249345aa0e356c781da361f8599a6ae63103f0c5f4754b2e8ce9b43c90e069aef908353b2db47722906bc097009d2fe651f1c9034d7782bd75a4a44050e46d7bcf93038ae1c1da11b5f07bbf8e1f56c26250b1b3636b5f884a5f0176bad7f1a1c74dad8c99e06f691686ce428e71b6cf62143c7e5b6aeb19fe68912bd8183287fec70205eba34fda3a873cdf753ac8389817610a93a04e5478de9a7246d157ac33033d12bb622f266ec6e332b816c87a4ff31adc191a5e7e548eed8ea93ddb5c77cf2ab8c1ab398bfd2ec86b3164fd546e41dbe3ec1054efb632c77fea51e6bd14c10185c8d963d84d9805faabc76bb5a1adbf6be795f4fc57d9bf3527cb0fa90afd1c1f83863161f33ea452c78c298ef1920bc54f7b24c4de64a3aa2312c338dc49efd3bb37029db5680049c85cbc803cc4ef1a013c435e529333f29faffe7fee727c86c08279e6d31005951c55dc571509ac997e24965caea41a3ebd6e99fa3280c0f499774abe2e85abb329a01f55692c12d6acc0bc81d870e584c58d5f3187b967bad9ceb02ab07a130f2864e7663926a1e8885ee76294eaf346e5c8cb0335bc58b1e6f47476448e081e25e09407e4ca6a7f00876843a1494e1c93778d23b7832d3a7d66fa69fb2589ba1f3af974d1dd651b5f7b718f924dc661ead149e9fbd6f11dced48e934f0"
    pt = decrypt(ct_hex)
    print("PT hex  :", hexlify(pt).decode())
    print("PT ascii:", pretty_ascii(pt))

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blog.diefunction.io/ctf/cyberyard-2025/reverse.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
