# Nullcon HackIM CTF 2025

Hey, fellow hackers! 🏴‍☠️&#x20;

In this writeup 📝, I’ll break down some of the exciting challenges solved by myself and my squad ***P4rad0x*** in the **Nullcon HackIM CTF 2025.**&#x20;

## Reversing

### Scrambled - 50

<figure><img src="/files/hwD2Bd2X5I7zyUue56CH" alt=""><figcaption><p>Challenge Description</p></figcaption></figure>

In this challenge, I was given a **Python script (main.py)** that encrypts a flag using **XOR and shuffling**. A **scrambled hex output** containing the encrypted flag. &#x20;

Our goal❓ **Recover the original flag!**.

Looking at `main.py`, we can break down the encryption process:

{% code overflow="wrap" %}

```python
import random

def encode_flag(flag, key):
    xor_result = [ord(c) ^ key for c in flag]

    chunk_size = 4
    chunks = [xor_result[i:i+chunk_size] for i in range(0, len(xor_result), chunk_size)]
    seed = random.randint(0, 10)
    random.seed(seed)
    random.shuffle(chunks)
    
    scrambled_result = [item for chunk in chunks for item in chunk]
    return scrambled_result, chunks

def main():
    flag = "REDACTED"
    key = REDACTED
    scrambled_result, _ = encode_flag(flag, key)
    print("result:", "".join([format(i, '02x') for i in scrambled_result]))

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

{% endcode %}

**XOR Encoding:**

* Each character of the flag is XORed with a **single-byte key**.
* This converts the flag into a scrambled list of integers.

**Chunking and Shuffling:**

* The encrypted data is split into **chunks of 4 bytes**.
* A **random seed (0-10)** is used to shuffle the chunks.
* The shuffled bytes are then **converted to hex** and printed as the output.

I was given this **scrambled hex output**:

{% code overflow="wrap" %}

```
1e78197567121966196e757e1f69781e1e1f7e736d6d1f75196e75191b646e196f6465510b0b0b57
```

{% endcode %}

To **undo** the encryption, I needed to **reverse** each step:

**Convert Hex to Bytes**&#x20;

* The scrambled hex string is converted back into a list of numbers.&#x20;

**Brute-force the Shuffling Seed**&#x20;

* Since the seed used for shuffling is between 0 and 10, I can try all possible seeds to reverse the shuffle order.&#x20;
* I reconstruct the original order by checking all possible shuffled indices.&#x20;

**Recover the XOR Key**&#x20;

* Since flags typically follow a format like "ENO{", I can use known plaintext attack to determine the XOR key.&#x20;
* Compare "ENO{" with the first 4 decrypted characters to derive the key.&#x20;

**Apply XOR Decryption**&#x20;

* Once the key is found, XOR it with all the scrambled bytes to reveal the flag.

<pre class="language-python" data-overflow="wrap"><code class="lang-python"><strong>import random
</strong>
scrambled_hex = "1e78197567121966196e757e1f69781e1e1f7e736d6d1f75196e75191b646e196f6465510b0b0b57"
scrambled_bytes = [int(scrambled_hex[i:i+2], 16) for i in range(0, len(scrambled_hex), 2)]
chunk = 4
n = len(scrambled_b) // chunk

# Try seeds from 0 to 10
for s in range(11):
    parts = []
    
    # Split into chunks
    for i in range(0, len(scrambled_b), chunk):
        parts.append(scrambled_b[i:i+chunk])

    random.seed(s)
    indices = list(range(n))
    random.shuffle(indices)

    # Reverse shuffle
    orig = [None] * n
    for i in range(n):
        orig[indices[i]] = parts[i]

    # Flatten list
    orig_xor = []
    for p in orig:
        for b in p:
            orig_xor.append(b)

    # Try to recover key using "ENO{"
    plain = "ENO{"
    key_try = []
    
    for i in range(len(plain)):
        key_try.append(orig_xor[i] ^ ord(plain[i]))

    # Ensure key is consistent
    if len(set(key_try)) == 1:  # Single-byte XOR key
        k = key_try[0]
        flag = ""

        for b in orig_xor:
            flag += chr(b ^ k)

        print(f"Possible flag: {flag} (Key: {k})")
        break
</code></pre>

When executing `dec.py`, it successfully **reverses the shuffle,** finds the **XOR key, and decrypts the flag**! 🎉

**ENO{5CR4M83L3D\_3GG5\_4R3\_1ND33D\_T45TY!!!}**

### Flag Checker - 50

<figure><img src="/files/wrEGbHayPzdGMZQYYx7q" alt=""><figcaption><p>Challenge Description</p></figcaption></figure>

This challenge gave me a **binary file**—a simple program that asks for input and checks if it’s the correct flag. But instead of guessing, I need to **dig into the code** and find the flag myself. Let’s go! 🚀

First, I ran the `file` command to see what I was dealing with:

{% code overflow="wrap" %}

```bash
  r1pp3r 🔱 god2eye ~/Documents/CTF/nullconctf25/rev 
  λ file flag_checker 
flag_checker: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4aca4cf0854ed6ff188432f4e2fd4352913b822d, for GNU/Linux 3.2.0, stripped
```

{% endcode %}

So, it’s a **64-bit ELF binary** and **stripped** (which means no function names—just raw assembly).

**Time to crack it open!** 🔥

I loaded the binary into **Ghidra**—a **software reverse engineering (SRE)** framework and found the **main function**. Here's what it does:

* Prompts for **user input**.
* Passes the input to a **flag-checking function**.

<figure><img src="/files/sEeCUjgqtwp9uxUrcbiT" alt=""><figcaption><p>Main Function</p></figcaption></figure>

* This function **encrypts the user input** and then **compares it to a stored encrypted flag** in the `.rodata` section.

And there it was—the **encrypted flag** hidden in memory. Here's the **encryption logic :**

**Each byte of the input gets modified**:

* It **adds the byte’s index** to itself.
* Then it **XORs** the result with `0x5A`.

**Bitwise shifts happen next**:

* The encrypted byte is **shifted left by 3**.
* OR’ed with the same byte **shifted right by 5**.

<div><figure><img src="/files/6NTYY6R70qBLImR1axBx" alt=""><figcaption><p>Flag Check Function</p></figcaption></figure> <figure><img src="/files/vG9UdDG739EETwPzxMKK" alt=""><figcaption><p>Encryption Function</p></figcaption></figure></div>

Now, I just needed to decrypt it!. I just had to **reverse the process** to get back the original flag!

Here's my Python script to **decrypt the flag**:

{% code overflow="wrap" %}

```python
def dec(enc_hex: str) -> str:

    enc_b = bytes.fromhex(enc_hex)
    dec_b = bytearray(len(enc_b))
    
    for i in range(len(enc_b)):
        # Reverse the bit shifts
        rot_back = ((enc_b[i] >> 3) | (enc_b[i] << 5)) & 0xFF
        # Subtract index value
        org_byte = (rot_back - i) & 0xFF
        # XOR with 0x5A
        dec_b[i] = org_byte ^ 0x5A
    
    return dec_b.decode(errors='ignore')  # Ignore errors if non-printable characters exist
# Encrypted input as hex string
enc_hex = "f8 a8 b8 21 60 73 90 83 80 c3 9b 80 ab 09 59 d3 21 d3 db d8 fb 49 99 e0 79 3c 4c 49 2c 29 cc d4 dc"

# Decrypt the message
dec_msg = dec(enc_hex)

print("Decrypted Message:", dec_msg)
```

{% endcode %}

I ran the script, and… **BOOM!💥**

**ENO{R3V3R53\_3NG1N33R1NG\_M45T3R!!!}**

## Web

### Paginator - 50

<figure><img src="/files/cLuBG5sC9Ls52VC3WJsz" alt=""><figcaption><p>Challenge Description</p></figcaption></figure>

In this challenge, I had to use **SQL injection** techniques to uncover the hidden flag. Let’s dive in!

Clicking on the provided link took me to a **paginated web application**—a site that loads data in chunks instead of displaying everything at once.

I also noticed a **"Source"** button that led me to: **`http://52.59.124.14:5012/?source`**

Here, I found the **source code** of the backend, which was a crucial clue.

<figure><img src="/files/kw5YCh8NnD51Jv06DrBl" alt=""><figcaption><p>Source code</p></figcaption></figure>

Reading through the code, I discovered an important detail:

🚨 **The flag (id = 1) was restricted!** 🚨

* The site’s database had an **"id"** field where each entry had a unique number.
* **Direct access to id = 1 (the flag) was blocked**—so I couldn’t just go to `?id=1`.
* However, there was a **vulnerability** in how the pagination feature handled input.

The pagination feature worked with two parameters:

```
?p=<page_number>,<max_items>
```

But here’s the catch: **the "max\_items" value wasn’t properly validated**.

I crafted a simple SQL injection payload:

```
?p=2,10 OR id=1
```

* `2,10` → Normal pagination parameters.
* `OR id=1` → A condition to **forcefully retrieve** the entry where `id=1` (which contains the flag).

By entering this into the URL: **`http://52.59.124.14:5012/?p=2,10%20OR%20id=1`**

Boom! 💥 After decoding got the flag!

<figure><img src="/files/hPT4qzWUWsgx1z47BK6X" alt=""><figcaption><p>Flag</p></figcaption></figure>

**ENO{SQL1\_W1th\_0uT\_C0mm4\_W0rks\_SomeHow!}**

## Misc

### Profound Thought - 50

<figure><img src="/files/mt5nuqUVigLY8YpHnMyZ" alt=""><figcaption><p>Challenge Description</p></figcaption></figure>

This challenge gave me a PNG image file, there was something hidden inside. But where? How?

First things first, I ran the usual checks:

🛠 **`exiftool`** – Nothing suspicious in the metadata.\
🛠 **`file` command** – Just a regular JPG file.

```bash
 r1pp3r 🔱 god2eye ~/Documents/CTF/nullconctf25/misc 
  λ file l5b245c11.png 
l5b245c11.png: PNG image data, 480 x 360, 8-bit/color RGB, non-interlaced
 r1pp3r 🔱 god2eye ~/Documents/CTF/nullconctf25/misc 
  λ exiftool l5b245c11.png 
ExifTool Version Number         : 12.76
File Name                       : l5b245c11.png
Directory                       : .
File Size                       : 222 kB
File Modification Date/Time     : 2025:02:01 22:51:58+05:30
File Access Date/Time           : 2025:02:04 15:33:54+05:30
File Inode Change Date/Time     : 2025:02:01 22:52:51+05:30
File Permissions                : -rwxrwx---
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 480
Image Height                    : 360
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Image Size                      : 480x360
Megapixels                      : 0.173

```

No hidden comments, no strange data, just a plain old image. Since metadata didn’t give me anything, I turned to a more powerful tool: **zsteg**.

**Zsteg** is also a tool used to detect LSB steganography only in the case of PNG and BMP images.

I ran:

<figure><img src="/files/bJ4cnq92ccSrhrl25imW" alt=""><figcaption><p>Zsteg output</p></figcaption></figure>

And BOOM! 💥 **A flag appeared!**

**ENO{57394n09r4phy\_15\_w4y\_c00l3r\_7h4n\_p0rn06r4phy}**

### USBnet - 50

<figure><img src="/files/RnYs47iRbGQ6kZuUSco7" alt=""><figcaption><p>Challenge Description</p></figcaption></figure>

This one started with a **pcapng file** (a packet capture file) containing USB traffic. First step?&#x20;

**Wireshark**—the go-to tool for network forensics!

Opening the file in Wireshark, I spotted several packets with **NCMA** and **NCM0** identifiers. Looking deeper, I found the USB device that used for this capture was a **Gigabit Ethernet adapter (AX88179) from ASIX Electronics Corp.** Cool

<figure><img src="/files/aHQUf8LPkeCNJ4UNIyv4" alt=""><figcaption><p>Wireshark Capture</p></figcaption></figure>

While scrolling through the packet list, I noticed something cool—a **PNG file signature** inside the hex data. That meant an **image was hidden in the traffic!**

<figure><img src="/files/LNcUdCoH6ZxsVuHajhHM" alt=""><figcaption><p>Packet with PNG file signature</p></figcaption></figure>

I grabbed the **hex data** of the packet containing the PNG signature and ran it through **CyberChef.**

<figure><img src="/files/og6gOdlAlA2KygQ48FAZ" alt=""><figcaption><p>CyberChef</p></figcaption></figure>

💥 Boom! A **QR code** appeared!

<figure><img src="/files/BWDhMi8nrX2hPHCtn4Ql" alt=""><figcaption><p>Extracted QR</p></figcaption></figure>

**ENO{USB\_ETHERNET\_ADAPTER\_ARE\_COOL\_N!C3}**


---

# 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://0xr1pp3r.gitbook.io/blogs/ctf-2025/nullcon-hackim-ctf-2025.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.
