# 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2Fbmc5inpGqtwEaakdChog%2FScrambled.jpg?alt=media&#x26;token=5cf93772-46d5-4909-a87a-8d1bf248073a" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FEmQt5V4ZW1tYTZRsCMBU%2FFlag%20Checker.jpg?alt=media&#x26;token=80eb795c-10c6-470d-9260-cb101932ee24" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FvTTo6JYBYpqNMzI7q17n%2Fflagcheckerenc.jpg?alt=media&#x26;token=2cfda2e4-9861-49db-a9f6-29efc0323db8" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2F7lTZ9UGjETNceDwRsVX0%2Fflagcheckerenc1.jpg?alt=media&#x26;token=8958f7f4-40be-47ee-8598-29c7116d33de" alt=""><figcaption><p>Flag Check Function</p></figcaption></figure> <figure><img src="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2Fd5cyIgXH6YGkwC3TPrtH%2Fflagcheckerenc2.jpg?alt=media&#x26;token=d7948d3f-3157-48a7-bbae-bcc054d7c04f" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FzXhGofE2XboWRtEWI41Z%2Fpaginator%20chal.jpg?alt=media&#x26;token=1f4287d3-880b-4476-a711-8dc440ab4648" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2F6b1uViBl7kVhdBECWFVH%2Fpagenator%20source.jpg?alt=media&#x26;token=278a508e-2bfb-4819-abf9-e701136ff7c9" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FYyDSFS6VxNflR0dH1S9d%2Fpaginator%20flag.jpg?alt=media&#x26;token=92bd179a-2e99-49ac-9d52-653293b3a01b" alt=""><figcaption><p>Flag</p></figcaption></figure>

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

## Misc

### Profound Thought - 50

<figure><img src="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FzcaWF1dXDlDiyFz0bBwo%2FProfound%20Thought.jpg?alt=media&#x26;token=0ded3e89-0645-407d-bb39-70a93995474d" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FUqUxdJ1JF4IEZQVwP78o%2Fprofoundthought%20flag.jpg?alt=media&#x26;token=892cdaca-6481-4b6e-8ec3-de763d94c300" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2Fs4AfrKs5gppd4Yk0YWyd%2FUSBnet.jpg?alt=media&#x26;token=bb40044f-d1bf-42ac-b267-bf183db2500e" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2FjbMflgQqoj8TVUsuyJx0%2Fusbnet%20wireshark.jpg?alt=media&#x26;token=f71f359c-6e80-4891-b486-431b4a06b31c" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2Fim42x1icP25fDhhEyymK%2Fusbnet%20png%20file.jpg?alt=media&#x26;token=a5f90316-cef6-4333-b855-5660ef10b0ad" 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="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2F5mFHDJLtWDNepLcO7JG5%2Fusbnet%20cyberchef.jpg?alt=media&#x26;token=d22ba418-f74a-4ad0-b592-174fd7f99dc9" alt=""><figcaption><p>CyberChef</p></figcaption></figure>

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

<figure><img src="https://2619072038-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyCrT4YxGad5HFRKWhUG8%2Fuploads%2Fcqie3oBq0Oc2AbaEPc63%2Fusbnet%20extracted_png.png?alt=media&#x26;token=e67ab283-5e39-4eaf-b753-6aa45a16a878" alt=""><figcaption><p>Extracted QR</p></figcaption></figure>

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