This is a writeup for the Soulcrabber challenge, part of the Hack the box's Cyberapocalypse CTF 2021, category Crypto.
Propmpt
Aliens heard of this cool newer language called Rust, and hoped the safety it offers could be used to improve their stream cipher.
Recon
We get a Rust script which applies a seemingly random XOR cypher to a flag.txt
file, and outputs it in hex to out.txt
. We also get the output file, so all we have to do is reverse the procedure.
use rand::{Rng,SeedableRng};
use rand::rngs::StdRng;
use std::fs;
use std::io::Write;
fn get_rng() -> StdRng {
let seed = 13371337;
return StdRng::seed_from_u64(seed);
}
fn rand_xor(input : String) -> String {
let mut rng = get_rng();
return input
.chars()
.into_iter()
.map(|c| format!("{:02x}", (c as u8 ^ rng.gen::<u8>())))
.collect::<Vec<String>>()
.join("");
}
fn main() -> std::io::Result<()> {
let flag = fs::read_to_string("flag.txt")?;
let xored = rand_xor(flag);
println!("{}", xored);
let mut file = fs::File::create("out.txt")?;
file.write(xored.as_bytes())?;
Ok(())
}
Sequence of events:
- Open a file
flag.txt
. - Generate a random key for XOR by using the seed
13371337
. - XOR the contents of
flag.txt
with the key. - Write out the result onto
out.txt
.
Although the code for XOR is generated by a random number generator, we immediately notice that the RNG is supplied with a known seed. This automatically means, that the numbers generated by it will follow the same sequence every time.
So all we have to do to reverse the process is to supply out.txt
as input (in hex) to the function, and we will get the original flag in hex as output.
Solution
Sequence:
- Read the hex value contents of
out.txt
. - Generate the pseudo-random key by using the same seed.
- XOR the contents of
out.txt
. - Write the XORed value onto
flag.txt
. - Represent the hex value of the key in ASCII using
xxd
.
use rand::{Rng,SeedableRng};
use rand::rngs::StdRng;
use std::fs;
use std::io::Write;
use std::io::prelude::*;
fn get_rng() -> StdRng {
let seed = 13371337;
return StdRng::seed_from_u64(seed);
}
fn rand_xor(input :&mut [u8]) -> String {
let mut rng = get_rng();
return input
// .chars()
.into_iter()
.map(|c| format!("{:02x}", (*c ^ rng.gen::<u8>())))
.collect::<Vec<String>>()
.join("");
}
fn main() -> std::io::Result<()> {
// we now read the file contents into a vector of u8 values
let mut file_content = Vec::new();
let mut file = fs::File::open("out.txt").expect("Unable to open file");
file.read_to_end(&mut file_content).expect("Unable to read");
let xored = rand_xor(&mut file_content);
println!("{}", xored);
let mut file = fs::File::create("flag.txt")?;
file.write(xored.as_bytes())?;
Ok(())
}
Of course, since this is the first time I had to deal with Rust, I had to install a lot of stuff first. After that, to generate the needed files to build the project, we run:
cargo init
in the project folder to generate a templatedCargo.toml
file.- Add
rand = '*'
under[dependencies]
insideCargo.toml
. - Run
cargo build
to build the project. - Run it with
cargo run
.
We now have the hex representation of the flag inside our flag.txt
file, so we can simply run
cat flag.txt | xxd -r -ps
and we get our flag
CHTB{mem0ry_s4f3_crypt0_f41l}
Thank you for reading!
The information in this blog, as well as all the tools, apps and libraries I develop are currently open source.
I would love to keep it this way, and you can help!
You can buy me a coffee from here, which will go towards the next all-nighter I pull off!
Or you can support me and my code monthly over at Github Sponsors!
Thanks!