I’m dropping a writeup for the Pico CTF 2025 challenges!️. I teamed up with my squad, P4rad0x. Let’s jump straight into the action! 💻🔥
Web
Cookie Monster Secret Recipe
A challenge named after everyone’s favorite blue, cookie-loving monster. The challenge name hints at cookies, implying that authentication or sensitive information might be stored within browser cookies.
Upon visiting the website, I inspected the cookies using the browser's developer tools (Application > Cookies).
The cookie value was base64-encoded, which is a common method of simple obfuscation.
Decoding the base64 string revealed the flag.
picoCTF{c00k1e_m0nster_l0ves_c00kies_057BCB51}
Heap-Dump
A web-based challenge involving an API where memory dumps might be accessible.
Navigated through the web interface and identified api-docs revealing various API endpoints.
One particular endpoint, /heapdump, caught my attention as it could contain sensitive memory data. Using curl, I accessed the /heapdump and filtered through the output with grep to extract the flag.
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25
λ curl http://verbal-sleep.picoctf.net:52248/heapdump | grep "picoCTF{"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
30 8748k 30 2690k 0 0 489k 0 0:00:17 0:00:05 0:00:12 633k
picoCTF{Pat!3nt_15_Th3_K3y_388d10f7}
100 8748k 100 8748k 0 0 1274k 0 0:00:06 0:00:06 --:--:-- 2430k
picoCTF{Pat!3nt_15_Th3_K3y_388d10f7}
n0s4n1ty 1
This challenge involved a file upload functionality that could be exploited for Remote Code Execution (RCE).
The website allowed users to upload files without restriction. I attempted uploading a basic PHP command injection script to execute arbitrary commands.
After successfully uploading the file, I accessed it via the browser and executed system commands. Using cat flag, I retrieved the flag from the server.
picoCTF{wh47_c4n_u_d0_wPHP_a4ca6ea0}
SSTI1
A web form suggested the possibility of Server-Side Template Injection (SSTI), a common vulnerability in improperly sanitized template engines.
Entered {{ 7*7 }} in the input field to confirm SSTI vulnerability. The output 49 verified that user input was being evaluated as code.
Once the vulnerability is confirmed, I can escalate it to execute system commands or read sensitive files. Extracted the flag using:
This challenge presented a Python script that dynamically sings a song, inserting the flag into the lyrics. The flag is stored in a file called flag.txt and is referenced within the script.
import re
import time
# Read in flag from file
flag = open('flag.txt', 'r').read()
secret_intro = \
'''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 + '\n'
song_flag_hunters = secret_intro +\
'''
[REFRAIN]
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 (Singalong here!);
RETURN
[VERSE1]
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?
REFRAIN;
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.
REFRAIN;
Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.
REFRAIN;
Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.
REFRAIN;
SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.
REFRAIN;
Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.
REFRAIN;
END;
'''
MAX_LINES = 100
def reader(song, startLabel):
lip = 0
start = 0
refrain = 0
refrain_return = 0
finished = False
# Get list of lyric lines
song_lines = song.splitlines()
# Find startLabel, refrain and refrain return
for i in range(0, len(song_lines)):
if song_lines[i] == startLabel:
start = i + 1
elif song_lines[i] == '[REFRAIN]':
refrain = i + 1
elif song_lines[i] == 'RETURN':
refrain_return = i
# Print lyrics
line_count = 0
lip = start
while not finished and line_count < MAX_LINES:
line_count += 1
for line in song_lines[lip].split(';'):
if line == '' and song_lines[lip] != '':
continue
if line == 'REFRAIN':
song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
lip = refrain
elif re.match(r"CROWD.*", line):
crowd = input('Crowd: ')
song_lines[lip] = 'Crowd: ' + crowd
lip += 1
elif re.match(r"RETURN [0-9]+", line):
lip = int(line.split()[1])
elif line == 'END':
finished = True
else:
print(line, flush=True)
time.sleep(0.5)
lip += 1
reader(song_flag_hunters, '[VERSE1]')
Understanding the code:
The script reads the flag from flag.txt and integrates it into the song lyrics.
It defines a song structure using labeled sections ([VERSE1], [REFRAIN], etc.).
The function reader(song, startLab``el) processes the song lyrics and follows control flow logic based on RETURN statements and user input.
The Crowd: prompt expects input, but simply providing RETURN 0;RETURN 0 skips unnecessary interaction and proceeds to reveal the flag.
The ';' is provided here because of for line in song_lines[lip].split(';'): splits the songs label with it and it will be converted into integer here elif re.match(r"RETURN [0-9]+", line): and it goes to the line "0" which is start of the program where it has the flag.
Connected to the remote instance via Netcat:
nc verbal-sleep.picoctf.net 59025
Observed the song lyrics being printed dynamically. Responded with RETURN 0;RETURN 0 when prompted, allowing the script to cycle through and eventually print the flag
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/Reversing
λ nc verbal-sleep.picoctf.net 59025
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: RETURN 0;RETURN 0
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: RETURN 0
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{70637h3r_f0r3v3r_a3d964ee}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
...
picoCTF{70637h3r_f0r3v3r_a3d964ee}
Forensics
RED
The first step was to analyze the image using forensic tools. PNG files often hide information through steganography.
The zsteg tool, commonly used for extracting hidden messages from PNG images, was employed for analysis.
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/Forensics
λ zsteg red.png
meta Poem .. text: "Crimson heart, vibrant and bold,\nHearts flutter at your sight.\nEvenings glow softly red,\nCherries burst with sweet life.\nKisses linger with your warmth.\nLove deep as merlot.\nScarlet leaves falling softly,\nBold in every stroke."
b1,rgba,lsb,xy .. text: "cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ=="
b1,rgba,msb,xy .. file: OpenPGP Public Key
b2,g,lsb,xy .. text: "ET@UETPETUUT@TUUTD@PDUDDDPE"
b2,rgb,lsb,xy .. file: OpenPGP Secret Key
b2,bgr,msb,xy .. file: OpenPGP Public Key
b2,rgba,lsb,xy .. file: OpenPGP Secret Key
b2,rgba,msb,xy .. text: "CIkiiiII"
b2,abgr,lsb,xy .. file: OpenPGP Secret Key
b2,abgr,msb,xy .. text: "iiiaakikk"
b3,rgba,msb,xy .. text: "#wb#wp#7p"
b3,abgr,msb,xy .. text: "7r'wb#7p"
b4,b,lsb,xy .. file: 0421 Alliant compact executable not stripped
Running zsteg red.png revealed a Base64-encoded string. Decoding the Base64 string revealed the flag.
There is so much unwanted data with frame length of "48" so i removed it and the remaining is the flag in wrong order.
nt_th4t } e1ff063 _34sy_t {1t_w4s picoCTF bh_4r_2
I assembled it as flag.
picoCTF{1t_w4snt_th4t_34sy_tbh_4r_2e1ff063}
Bitlocker-1
First step for Identifying the file type I used the file command to confirm that the provided .dd file was an MBR boot sector with BitLocker encryption.
Secondly for the extracting password hashes I used bitlocker2john to extract BitLocker password hashes from the image file.
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/Forensics
λ bitlocker2john -i bitlocker-1.dd > bithash
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/Forensics
λ cat bithash
Encrypted device bitlocker-1.dd opened, size 100MB
Salt: 2b71884a0ef66f0b9de049a82a39d15b
RP Nonce: 00be8a46ead6da0106000000
RP MAC: a28f1a60db3e3fe4049a821c3aea5e4b
RP VMK: a1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
UP Nonce: d04d9c58eed6da010a000000
UP MAC: 68156e51e53f0a01c076a32ba2b2999a
UP VMK: fffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
User Password hash:
$bitlocker$0$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
Hash type: User Password with MAC verification (slower solution, no false positives)
$bitlocker$1$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
Hash type: Recovery Password fast attack
$bitlocker$2$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
Hash type: Recovery Password with MAC verification (slower solution, no false positives)
$bitlocker$3$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
For cracking the hash, I used hashcat , which is a popular tool to for cracking hashes. It successfully cracked the hash and retrieved the password -> jacqueline
After that, I created a mount point and used dislocker tool to unlock BitLocker.
In this challenge, I had to connect to a remote server using netcat. Upon connection, the server presented a series of hashed passwords that I needed to crack in order to progress. The hashes were of different types, including MD5, SHA-1, and SHA-256.
Upon connecting to the server:
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/Forensics
λ nc verbal-sleep.picoctf.net 57192
Welcome!! Looking For the Secret?
I received the following prompts:
MD5 Hash
We have identified a hash: 482c811da5d5b4bc6d497ffa98491e38
Enter the password for identified hash: password123
Correct! You've cracked the MD5 hash with no secret found!
Hash: 482c811da5d5b4bc6d497ffa98491e38
Cracked password: password123
Response: "Correct! You've cracked the MD5 hash with no secret found!"
SHA-1 Hash
Flag is yet to be revealed!! Crack this hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3
Enter the password for the identified hash: letmein
Correct! You've cracked the SHA-1 hash with no secret found!
Hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3
Cracked password: letmein
Response: "Correct! You've cracked the SHA-1 hash with no secret found!"
SHA-256 Hash
Almost there!! Crack this hash: 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745
Enter the password for the identified hash: qwerty098
Correct! You've cracked the SHA-256 hash with a secret found.
The encryption process in encrypt.py follows the standard RSA encryption technique:
from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
"""
Generates RSA key with k bits
"""
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
if __name__ == "__main__":
flag = open('flag.txt', 'r').read()
flag = flag.strip()
N, cypher = main(flag)
print("N:", N)
print("e:", e)
print("cyphertext:", cypher)
exit()
The decrypted message revealed the flag. picoCTF{tw0_1$_pr!m33991588e}
Binary Exploitation
PIE TIME
In this challenge, I was provided with a remote connection to a binary running on a server:
$ nc rescued-float.picoctf.net 49325
Additionally, we receive two downloadable files:
Analyzing the Source Code
The provided vuln.c code snippet is as follows:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
int win() {
FILE *fptr;
char c;
printf("You won!\n");
// Open file
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
printf("Cannot open file.\n");
exit(0);
}
// Read contents from file
c = fgetc(fptr);
while (c != EOF)
{
printf ("%c", c);
c = fgetc(fptr);
}
printf("\n");
fclose(fptr);
}
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();
}
Understanding the code:
The program prints the address of the main() function.
It prompts the user for an address to jump to.
It then treats the given input as a function pointer and executes it.
If an incorrect address is given, a segfault handler prevents crashes.
The win() function, which prints the flag, is not explicitly called anywhere in the program.
The goal? Find and provide the correct address for win() so the program jumps to it!
Bypassing Position-Independent Executable (PIE)
Since PIE is enabled, the memory addresses change on each execution. However, the offset between functions remains the same. To determine this offset, we analyze the binary using Radare2:
When we connect to the remote server, it prints the dynamically assigned main() address.
Thus, we can calculate win() address:
win_address = main_address - 150
Connecting to the challenge server:
r1pp3r 🔱 god2eye ~/Documents/CTF/picoctf25/bin_exp
λ nc rescued-float.picoctf.net 49325
Address of main: 0x576b49c6a33d
Enter the address to jump to, ex => 0x12345: 0x576b49c6a2a7
Your input: 576b49c6a2a7
You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_3d38fb4b}
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_3d38fb4b}
General Skills
Rust Fixme 1
This is a challenges which presents a piece of Rust code with several syntax and logical errors. The goal is to identify and fix these issues to successfully decrypt an encrypted message.
Given Rust code with errors:
use xor_cryptor::XORCryptor;
fn main() {
// Key for decryption
let key = String::from("CSUCKS") // How do we end statements in Rust?
// Encrypted flag values
let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6", "50", "d3", "35"];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
// Create decrpytion object
let res = XORCryptor::new(&key);
if res.is_err() {
ret; // How do we return in rust?
}
let xrc = res.unwrap();
// Decrypt flag and print it out
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
println!(
":?", // How do we print out a variable in the println function?
String::from_utf8_lossy(&decrypted_buffer)
);
}
Fixed Rust code:
use xor_cryptor::XORCryptor;
fn main() {
// Key for decryption
let key = String::from("CSUCKS"); // Fixed missing semicolon
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61",
"25", "7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6",
"50", "d3", "35",
];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return; // Fixed incorrect "ret;" to "return;"
}
let xrc = res.unwrap();
// Decrypt flag and print it out
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
println!(
"{}",
String::from_utf8_lossy(&decrypted_buffer) // Fixed incorrect ":?" to "{}"
);
}
Replaced ret; with return; → Rust does not have a ret keyword.
Corrected println! syntax → Used {} instead of :? for formatted output.
Passed encrypted_buffer as a reference → Ensures proper memory handling.
picoCTF{4r3_y0u_4_ru$t4c30n_n0w?}
Rust Fixme 2
In this it requires modifying a function so that it correctly updates a mutable string passed as an argument.
Given Rust code with errors:
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer:Vec<u8>, borrowed_string: &String){ // How do we pass values to a function that we want to change?
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decrpytion object
let res = XORCryptor::new(&key);
if res.is_err() {
return; // How do we return in rust?
}
let xrc = res.unwrap();
// Decrypt flag and print it out
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9", "42", "5b"];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let party_foul = String::from("Using memory unsafe languages is a: "); // Is this variable changeable?
decrypt(encrypted_buffer, &party_foul); // Is this the correct way to pass a value to a function so that it can be changed?
}
Fixed Rust code:
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) { // Fixed: Use &mut String for modification
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return;
}
let xrc = res.unwrap();
// Decrypt flag and append it
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61",
"25", "0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9",
"42", "5b",
];
// Convert hex strings to bytes
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: "); // Fixed: Make this mutable
decrypt(encrypted_buffer, &mut party_foul); // Fixed: Pass mutable reference
}
Key Fixes and Explanation:
Used &mut String → Allows modification of borrowed_string inside the function.
Declared party_foul as mutable → let mut party_foul = String::from("...").
Passed party_foul as &mut → Enables mutation within decrypt() .
picoCTF{4r3_y0u_h4v1n5_fun_y31?}
Rust Fixme 3
This challenge focuses on eliminating unnecessary unsafe operations and ensuring safe memory handling in Rust.
Given Rust code with errors:
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return;
}
let xrc = res.unwrap();
// Did you know you have to do "unsafe operations in Rust?
// https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
// Even though we have these memory safe languages, sometimes we need to do things outside of the rules
// This is where unsafe rust comes in, something that is important to know about in order to keep things in perspective
// unsafe {
// Decrypt the flag operations
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
// Creating a pointer
let decrypted_ptr = decrypted_buffer.as_ptr();
let decrypted_len = decrypted_buffer.len();
// Unsafe operation: calling an unsafe function that dereferences a raw pointer
let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);
borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
// }
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3", "61", "f9", "35"];
// Convert the hexadecimal strings to bytes and collect them into a vector
let encrypted_buffer: Vec<u8> = hex_values.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: ");
decrypt(encrypted_buffer, &mut party_foul);
}
Fixed Rust code:
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
// Key for decryption
let key = String::from("CSUCKS");
// Editing our borrowed value
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
// Create decryption object
let res = XORCryptor::new(&key);
if res.is_err() {
return;
}
let xrc = res.unwrap();
// Decrypt the flag
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
// Append decrypted flag to string
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
// Encrypted flag values
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01",
"35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3",
"61", "f9", "35",
];
// Convert hex strings to bytes
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: ");
decrypt(encrypted_buffer, &mut party_foul);
}
Key Fixes and Explanation:
Removed unnecessary raw pointer operations → Rust is memory-safe by default; avoid unsafe unless absolutely needed.
Used a simple reference (&decrypted_buffer) instead of unsafe slices.
Preserved Rust’s memory safety principles → The corrected code works efficiently without unsafe blocks.
picoCTF{n0w_y0uv3_f1x3d_1h3m_411}
In this challenge, I was provided with a PNG file and the word "RED" is all over the challenge.
The challenge provided a PCAP (Packet Capture) file . Given the nature of network forensic challenges, I anticipated the flag might be hidden within packet data.
The challenge provided a Windows BitLocker-encrypted image file (MBR Boot Sector File) . The goal was to unlock and extract the flag.
In this challenge, I was given an RSA encryption script and had to decrypt an encrypted flag. The server provided the RSA parameters: N (modulus), e (public exponent), and the ciphertext.
So, i used an online tool which has RSA decryption function.