> For the complete documentation index, see [llms.txt](https://blog.diefunction.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blog.diefunction.io/ctf/cyberyard-2025/reverse.md).

# 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))

```
