picoCTF Writeup: Rust Fixme 3 - Stepping into Unsafe Rust
picoCTF Writeup: Rust Fixme 3 - Stepping into Unsafe Rust
tar -xzvf filename.tar.gz
-x(eXtract): Tellstarto extract the files from the archive.-z(gZip): Tellstarto decompress the archive usinggzip(since it is a.gzfile).-v(Verbose): Optional, but highly recommended. It lists all the files on your screen as they are being extracted so you can see the progress.-f(File): Specifies the filename of the archive. Note: This flag must always be the last one right before the filename.
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);
}
}
So I don't know Rust : ( But I run the program : )
error[E0133]: call to unsafe function `std::slice::from_raw_parts` is unsafe and requires unsafe function or block
--> src/main.rs:31:31
|
31 | ...lice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
I asked Gemini and ...
Gemini said
This is a classic Rust security feature in action! The compiler is doing exactly what it was designed to do: stopping you from interacting with raw memory without explicit permission.
In languages like C or C++, you can freely read from any memory pointer, which often leads to buffer overflows or out-of-bounds read vulnerabilities. Rust prevents this by default. The function std::slice::from_raw_parts takes a raw pointer (decrypted_ptr) and a length (decrypted_len) and turns it into a usable Rust slice. However, the Rust compiler cannot mathematically guarantee that the pointer is actually valid or that the length is accurate.
To fix this, you have to take manual responsibility for the memory safety by wrapping the call in an unsafe block.
let slice = unsafe {
std::slice::from_raw_parts(decrypted_ptr, decrypted_len)
};
And then...
┌──(nirmal㉿NirmalsFLOWX16)-[~/CTF/RustFixme_3/fixme3]
└─$ cargo run
Compiling rust_proj v0.1.0 (/home/nirmal/CTF/RustFixme_3/fixme3)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/rust_proj`
Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{n0w_y0uv3_f1x3d_1h3m_411}
So in the end I did learn something about Rust though. XD