diff --git a/submissions/challenge1/michal_racinowski/.gitignore b/submissions/challenge1/michal_racinowski/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/submissions/challenge1/michal_racinowski/Cargo.lock b/submissions/challenge1/michal_racinowski/Cargo.lock new file mode 100644 index 0000000..b65ebbd --- /dev/null +++ b/submissions/challenge1/michal_racinowski/Cargo.lock @@ -0,0 +1,252 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "easy" +version = "0.1.0" +dependencies = [ + "aes", + "base64", + "hex", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hard" +version = "0.1.0" +dependencies = [ + "base64", + "easy", + "hex", + "rand", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "medium" +version = "0.1.0" +dependencies = [ + "easy", + "hex", + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/submissions/challenge1/michal_racinowski/Cargo.toml b/submissions/challenge1/michal_racinowski/Cargo.toml new file mode 100644 index 0000000..c8b2691 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "3" +members = ["easy", "hard", "medium"] diff --git a/submissions/challenge1/michal_racinowski/README.md b/submissions/challenge1/michal_racinowski/README.md new file mode 100644 index 0000000..dc34f69 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/README.md @@ -0,0 +1,47 @@ +# Coding Challenge #1 + +Solutions by Michał Racinowski (450260) + +The workspace contains three crates with solutions to the challenges. + +## CBC mode encryption (10; easy) + +To decrypt the file from the challenge execute: + +``` +cargo run --bin=easy decrypt "YELLOW SUBMARINE" "0000000000000000" easy/10.txt +``` + +The package also allows for encryption: + +``` +cargo run --bin=easy encrypt "YELLOW SUBMARINE" "0000000000000000" easy/ptx.txt +``` + +Usage of the package is available with command: + +``` +cargo run --bin=easy +``` + +## CBC bitflipping attack (16; medium) + +To run the attack execute: + +``` +cargo run --bin=medium +``` + +## CBC padding oracle attack (17; hard) + +To run the solution execute: + +``` +cargo run --bin=hard +``` + +It is also possible to run the challenge on provided plaintext: + +``` +cargo run --bin=hard 'Hello world!' +``` diff --git a/submissions/challenge1/michal_racinowski/easy/10.txt b/submissions/challenge1/michal_racinowski/easy/10.txt new file mode 100644 index 0000000..f0802a5 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/easy/10.txt @@ -0,0 +1,64 @@ +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 diff --git a/submissions/challenge1/michal_racinowski/easy/Cargo.toml b/submissions/challenge1/michal_racinowski/easy/Cargo.toml new file mode 100644 index 0000000..8f48c55 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/easy/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "easy" +version = "0.1.0" +edition = "2024" + +[dependencies] +aes = "0.8.4" +base64 = "0.22.1" +hex = "0.4.3" diff --git a/submissions/challenge1/michal_racinowski/easy/ptx.txt b/submissions/challenge1/michal_racinowski/easy/ptx.txt new file mode 100644 index 0000000..540ba50 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/easy/ptx.txt @@ -0,0 +1,11 @@ +Hello world! + +Ehrsam, Meyer, Smith and Tuchman invented the cipher block chaining (CBC) mode of operation in 1976. In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted. This way, each ciphertext block depends on all plaintext blocks processed up to that point. To make each message unique, an initialization vector must be used in the first block. + +CBC has been the most commonly used mode of operation. Its main drawbacks are that encryption is sequential (i.e., it cannot be parallelized), and that the message must be padded to a multiple of the cipher block size. One way to handle this last issue is through the method known as ciphertext stealing. Note that a one-bit change in a plaintext or initialization vector (IV) affects all following ciphertext blocks. + +Decrypting with the incorrect IV causes the first block of plaintext to be corrupt but subsequent plaintext blocks will be correct. This is because each block is XORed with the ciphertext of the previous block, not the plaintext, so one does not need to decrypt the previous block before using it as the IV for the decryption of the current one. This means that a plaintext block can be recovered from two adjacent blocks of ciphertext. As a consequence, decryption can be parallelized. Note that a one-bit change to the ciphertext causes complete corruption of the corresponding block of plaintext, and inverts the corresponding bit in the following block of plaintext, but the rest of the blocks remain intact. This peculiarity is exploited in different padding oracle attacks, such as POODLE. + +Explicit initialization vectors take advantage of this property by prepending a single random block to the plaintext. Encryption is done as normal, except the IV does not need to be communicated to the decryption routine. Whatever IV decryption uses, only the random block is "corrupted". It can be safely discarded and the rest of the decryption is the original plaintext. + +From Wikipedia, https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC) diff --git a/submissions/challenge1/michal_racinowski/easy/src/lib.rs b/submissions/challenge1/michal_racinowski/easy/src/lib.rs new file mode 100644 index 0000000..dcb5caa --- /dev/null +++ b/submissions/challenge1/michal_racinowski/easy/src/lib.rs @@ -0,0 +1,124 @@ +use aes::Aes128; +use aes::cipher::{ + BlockDecrypt, BlockEncrypt, BlockSizeUser, InvalidLength, KeyInit, generic_array::GenericArray, +}; + +pub fn xor_with(buffer: &mut [u8], other: &[u8]) { + for (a, b) in std::iter::zip(&mut *buffer, other) { + *a ^= b; + } +} + +pub fn split_to_blocks( + mut data: &mut [u8], +) -> Option::BlockSize>>> { + let mut blocks = Vec::new(); + + for _ in (0..data.len()).step_by(16) { + let (block, rest) = data.split_at_mut(16); + + blocks.push(GenericArray::from_mut_slice(block)); + data = rest; + } + + Some(blocks) +} + +pub struct Aes128Cbc { + cipher: Aes128, + iv: GenericArray::BlockSize>, +} + +impl Aes128Cbc { + pub fn new(key: &[u8], iv: &[u8]) -> Result { + Ok(Self { + cipher: Aes128::new_from_slice(key)?, + iv: *GenericArray::from_slice(iv), + }) + } + + pub fn encrypt(&self, ptx: &[u8]) -> Vec { + let pad = 16 - ptx.len() % 16; + let pad: u8 = pad.try_into().unwrap(); + + let mut ctx = ptx.to_vec(); + for _ in 0..(pad as usize) { + ctx.push(pad); + } + + let mut blocks = split_to_blocks(&mut ctx).unwrap(); + + for i in 0..blocks.len() { + let prev = if i == 0 { self.iv } else { *blocks[i - 1] }; + + xor_with(blocks[i], &prev); + self.cipher.encrypt_block(blocks[i]); + } + + ctx + } + + pub fn decrypt(&self, ctx: &[u8]) -> Option> { + let mut ptx = ctx.to_vec(); + + let mut blocks = split_to_blocks(&mut ptx)?; + + for i in (0..blocks.len()).rev() { + let prev = if i == 0 { self.iv } else { *blocks[i - 1] }; + + self.cipher.decrypt_block(blocks[i]); + xor_with(blocks[i], &prev); + } + + let pad = ptx[ptx.len() - 1]; + + if !(1..=16).contains(&pad) { + return None; + } + + for _ in 0..(pad as usize) { + if ptx.pop() != Some(pad) { + return None; + } + } + + Some(ptx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let mut left = Vec::from_iter(1_u8..22); + let right = [42_u8; 22].to_vec(); + xor_with(&mut left, &right); + assert_eq!( + left, + [ + 43, 40, 41, 46, 47, 44, 45, 34, 35, 32, 33, 38, 39, 36, 37, 58, 59, 56, 57, 62, 63 + ] + ) + } + + #[test] + fn test_split_to_blocks() { + let mut data = vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + assert_eq!( + split_to_blocks(&mut data), + Some(vec![ + GenericArray::from_mut_slice(&mut [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + ]), + GenericArray::from_mut_slice(&mut [ + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + ]), + ]) + ); + } +} diff --git a/submissions/challenge1/michal_racinowski/easy/src/main.rs b/submissions/challenge1/michal_racinowski/easy/src/main.rs new file mode 100644 index 0000000..49d7fef --- /dev/null +++ b/submissions/challenge1/michal_racinowski/easy/src/main.rs @@ -0,0 +1,74 @@ +use base64::prelude::*; +use easy::Aes128Cbc; +use std::ffi::{OsStr, OsString}; + +enum Cmd { + Encrypt, + Decrypt, +} + +fn main() { + let args: Vec = std::env::args_os().collect(); + + if args.len() != 5 { + eprintln!("Usage: easy encrypt KEY IV FILE"); + return; + } + + let cmd = if args[1] == OsStr::new("encrypt") { + Cmd::Encrypt + } else if args[1] == OsStr::new("decrypt") { + Cmd::Decrypt + } else { + eprintln!("Invalid command {:?}", args[1]); + return; + }; + + let key = &args[2]; + let iv = &args[3]; + let path = &args[4]; + + let Ok(cipher) = Aes128Cbc::new(key.as_encoded_bytes(), iv.as_encoded_bytes()) else { + eprintln!( + "Invalid encryption parameters key: {:?} or iv: {:?}", + key, iv + ); + return; + }; + + let bytes = match std::fs::read(path) { + Ok(bytes) => bytes, + Err(e) => { + eprintln!("Error reading the file {:?}: {}", path, e); + return; + } + }; + + match cmd { + Cmd::Encrypt => { + let ctx = cipher.encrypt(&bytes); + + println!("{}", BASE64_STANDARD.encode(ctx)); + } + + Cmd::Decrypt => { + let clean: Vec = bytes + .into_iter() + .filter(|byte| base64::alphabet::STANDARD.as_str().contains(*byte as char)) + .collect(); + let ctx = BASE64_STANDARD.decode(clean).unwrap(); + + let Some(ptx) = cipher.decrypt(&ctx) else { + eprintln!("Invalid ciphertext"); + return; + }; + + if let Ok(string) = String::from_utf8(ptx.clone()) { + println!("{}", string); + } else { + eprintln!("Warning: Invalid characters in the output"); + println!("{}", hex::encode(&ptx)); + } + } + } +} diff --git a/submissions/challenge1/michal_racinowski/hard/Cargo.toml b/submissions/challenge1/michal_racinowski/hard/Cargo.toml new file mode 100644 index 0000000..53d90b9 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/hard/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hard" +version = "0.1.0" +edition = "2024" + +[dependencies] +base64 = "0.22.1" +easy = { version = "0.1.0", path = "../easy" } +hex = "0.4.3" +rand = "0.9.2" diff --git a/submissions/challenge1/michal_racinowski/hard/src/lib.rs b/submissions/challenge1/michal_racinowski/hard/src/lib.rs new file mode 100644 index 0000000..6d9922e --- /dev/null +++ b/submissions/challenge1/michal_racinowski/hard/src/lib.rs @@ -0,0 +1,69 @@ +const BLOCK_SIZE: usize = 16; + +pub fn padding_oracle_attack( + iv: &[u8], + ctx: &[u8], + oracle: impl Fn(&[u8], &[u8]) -> bool, +) -> Option> { + let mut tmp = iv.to_vec(); + tmp.extend_from_slice(ctx); + let ctx = tmp; + + let mut ptx = ctx.clone(); + let mut query = ctx.clone(); + + for i in (BLOCK_SIZE..ctx.len()).rev() { + let pad = BLOCK_SIZE - i % BLOCK_SIZE; + let pad: u8 = pad.try_into().unwrap(); + + let mut found = false; + + for k in 0..(pad - 1) { + let offset = i + 1 + k as usize; + query[offset - BLOCK_SIZE] = ptx[offset] ^ ctx[offset - BLOCK_SIZE] ^ pad; + } + + for j in 0..=u8::MAX { + query[i - BLOCK_SIZE] = j; + + if !oracle(&query[..BLOCK_SIZE], &query[BLOCK_SIZE..]) { + continue; + } + + if pad == 1 { + query[i - BLOCK_SIZE - 1] ^= u8::MAX; + + let res = oracle(&query[..BLOCK_SIZE], &query[BLOCK_SIZE..]); + + query[i - BLOCK_SIZE - 1] ^= u8::MAX; + + if !res { + continue; + } + } + + found = true; + + ptx[i] = query[i - BLOCK_SIZE] ^ pad ^ ctx[i - BLOCK_SIZE]; + } + + if !found { + return None; + } + + if pad as usize == BLOCK_SIZE { + query.truncate(query.len() - BLOCK_SIZE); + + //for j in (query.len() - BLOCK_SIZE)..query.len() { + // query[j] = ctx[j]; + //} + + let query_len = query.len(); + + query[(query_len - BLOCK_SIZE)..] + .copy_from_slice(&ctx[(query_len - BLOCK_SIZE)..query_len]); + } + } + + Some(ptx[BLOCK_SIZE..].to_vec()) +} diff --git a/submissions/challenge1/michal_racinowski/hard/src/main.rs b/submissions/challenge1/michal_racinowski/hard/src/main.rs new file mode 100644 index 0000000..1294d7c --- /dev/null +++ b/submissions/challenge1/michal_racinowski/hard/src/main.rs @@ -0,0 +1,66 @@ +//use base64::{Engine as _, engine::general_purpose::URL_SAFE}; +use base64::prelude::*; +use easy::Aes128Cbc; +use hard::padding_oracle_attack; +use rand::{CryptoRng, Rng, SeedableRng, rngs::StdRng}; + +const BLOCK_SIZE: usize = 16; +const MESSAGES: [&str; 10] = [ + "MDAwMDAwTm93IHRoYXQgdGhlIHBhcnR5IGlzIGp1bXBpbmc=", + "MDAwMDAxV2l0aCB0aGUgYmFzcyBraWNrZWQgaW4gYW5kIHRoZSBWZWdhJ3MgYXJlIHB1bXBpbic=", + "MDAwMDAyUXVpY2sgdG8gdGhlIHBvaW50LCB0byB0aGUgcG9pbnQsIG5vIGZha2luZw==", + "MDAwMDAzQ29va2luZyBNQydzIGxpa2UgYSBwb3VuZCBvZiBiYWNvbg==", + "MDAwMDA0QnVybmluZyAnZW0sIGlmIHlvdSBhaW4ndCBxdWljayBhbmQgbmltYmxl", + "MDAwMDA1SSBnbyBjcmF6eSB3aGVuIEkgaGVhciBhIGN5bWJhbA==", + "MDAwMDA2QW5kIGEgaGlnaCBoYXQgd2l0aCBhIHNvdXBlZCB1cCB0ZW1wbw==", + "MDAwMDA3SSdtIG9uIGEgcm9sbCwgaXQncyB0aW1lIHRvIGdvIHNvbG8=", + "MDAwMDA4b2xsaW4nIGluIG15IGZpdmUgcG9pbnQgb2g=", + "MDAwMDA5aXRoIG15IHJhZy10b3AgZG93biBzbyBteSBoYWlyIGNhbiBibG93", +]; + +#[allow(clippy::type_complexity)] +fn make_challenge( + mut rng: impl Rng + CryptoRng + SeedableRng, + ptx: &str, +) -> (Vec, Vec, impl Fn(&[u8], &[u8]) -> bool) { + let key: [u8; BLOCK_SIZE] = rng.random(); + let iv: [u8; BLOCK_SIZE] = rng.random(); + + let ctx = Aes128Cbc::new(&key, &iv).unwrap().encrypt(ptx.as_bytes()); + + (iv.to_vec(), ctx, move |iv: &[u8], ctx: &[u8]| { + if let Ok(c) = Aes128Cbc::new(&key, iv) { + c.decrypt(ctx).is_some() + } else { + false + } + }) +} + +fn run_challenge(ptx: &str) { + eprintln!("\x1b[1mOriginal plaintext:\x1b[0m \x1b[92m{:?}\x1b[0m", ptx); + let (iv, ctx, oracle) = make_challenge(StdRng::from_os_rng(), ptx); + eprintln!( + "\x1b[1mCiphertext:\x1b[0m (\x1b[93m{}\x1b[0m, \x1b[93m{}\x1b[0m)", + hex::encode(&iv), + hex::encode(&ctx) + ); + let out = padding_oracle_attack(&iv, &ctx, oracle).unwrap(); + + println!("{}", String::from_utf8(out).unwrap()); +} + +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() == 2 { + run_challenge(&args[1]); + return; + } + + for message in MESSAGES { + let message = BASE64_STANDARD.decode(message).unwrap(); + let message = String::from_utf8(message).unwrap(); + run_challenge(&message); + } +} diff --git a/submissions/challenge1/michal_racinowski/medium/Cargo.toml b/submissions/challenge1/michal_racinowski/medium/Cargo.toml new file mode 100644 index 0000000..39028a5 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/medium/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "medium" +version = "0.1.0" +edition = "2024" + +[dependencies] +easy = { version = "0.1.0", path = "../easy" } +hex = "0.4.3" +rand = "0.9.2" diff --git a/submissions/challenge1/michal_racinowski/medium/src/lib.rs b/submissions/challenge1/michal_racinowski/medium/src/lib.rs new file mode 100644 index 0000000..197b5e0 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/medium/src/lib.rs @@ -0,0 +1,33 @@ +use easy::xor_with; + +const BLOCK_SIZE: usize = 16; + +pub fn cbc_bit_flipping_attack( + prefix: &[u8], + _suffix: &[u8], + target: &[u8], + mut oracle: impl FnMut(&[u8]) -> Option<(Vec, Vec)>, +) -> Option<(Vec, Vec)> { + if target.len() != BLOCK_SIZE { + return None; + } + + let prefix_len = prefix.len() + BLOCK_SIZE - prefix.len() % BLOCK_SIZE; + + let mut query = vec![0x41; prefix_len - prefix.len()]; + query.extend_from_slice(&[0x42; BLOCK_SIZE]); + query.extend_from_slice(&[0x43; BLOCK_SIZE]); + + let (iv, mut ctx) = oracle(&query)?; + + let mut block = ctx.split_off(prefix_len); + let mut rest = block.split_off(BLOCK_SIZE); + + xor_with(&mut block, target); + xor_with(&mut block, &[0x43; BLOCK_SIZE]); + + ctx.append(&mut block); + ctx.append(&mut rest); + + Some((iv, ctx)) +} diff --git a/submissions/challenge1/michal_racinowski/medium/src/main.rs b/submissions/challenge1/michal_racinowski/medium/src/main.rs new file mode 100644 index 0000000..b706a89 --- /dev/null +++ b/submissions/challenge1/michal_racinowski/medium/src/main.rs @@ -0,0 +1,95 @@ +use easy::Aes128Cbc; +use medium::cbc_bit_flipping_attack; +use rand::{CryptoRng, Rng, SeedableRng, rngs::StdRng}; + +const BLOCK_SIZE: usize = 16; +const PREFIX: &[u8] = "comment1=cooking%20MCs;userdata=".as_bytes(); +const TARGET: &[u8] = ";admin=true;x=y;".as_bytes(); +const SUFFIX: &[u8] = ";comment2=%20like%20a%20pound%20of%20bacon".as_bytes(); + +#[allow(clippy::type_complexity)] +fn make_challenge( + mut rng: impl Rng + CryptoRng + SeedableRng + 'static, +) -> ( + impl FnMut(&[u8]) -> Option<(Vec, Vec)>, + impl Fn(&[u8], &[u8]) -> bool, +) { + let key: [u8; BLOCK_SIZE] = rng.random(); + + ( + move |input: &[u8]| { + println!( + "\x1b[1mReceived userdata:\x1b[0m (\x1b[93m{}\x1b[0m)", + hex::encode(input) + ); + + for byte in input { + if *byte == 0x3b || *byte == 0x3d { + // ';' / '=' + return None; + } + } + + let mut ptx: Vec = PREFIX.to_vec(); + ptx.extend_from_slice(input); + ptx.extend_from_slice(SUFFIX); + + println!( + "\x1b[1mGenerated token:\x1b[0m \x1b[93m{}\x1b[0m", + hex::encode(&ptx) + ); + if let Ok(string) = String::from_utf8(ptx.clone()) { + println!("\x1b[1mDecoded token:\x1b[0m \x1b[93m{}\x1b[0m", string); + } + + let iv: [u8; BLOCK_SIZE] = rng.random(); + + Some(( + iv.to_vec(), + Aes128Cbc::new(&key, &iv).unwrap().encrypt(&ptx), + )) + }, + move |iv: &[u8], ctx: &[u8]| { + println!( + "\x1b[1mReceived (encrypted) token:\x1b[0m (\x1b[93m{}\x1b[0m, \x1b[93m{}\x1b[0m)", + hex::encode(iv), + hex::encode(ctx) + ); + + let Ok(c) = Aes128Cbc::new(&key, iv) else { + println!("\x1b[1mInvalid IV\x1b[0m"); + return false; + }; + + let Some(mut ptx) = c.decrypt(ctx) else { + println!("\x1b[1mInvalid padding\x1b[0m"); + return false; + }; + + for byte in &mut ptx { + if *byte < 32 || *byte > 127 { + *byte = 0x3f; // '?' + } + } + + let ptx = String::from_utf8(ptx).unwrap(); + + println!("\x1b[1mDecrypted token: \x1b[0m\x1b[93m{:?}\x1b[0m", ptx); + + ptx.contains(";admin=true;") + }, + ) +} + +fn main() { + let (oracle, evaluator) = make_challenge(StdRng::from_os_rng()); + + let (iv, ctx) = cbc_bit_flipping_attack(PREFIX, SUFFIX, TARGET, oracle).unwrap(); + + if evaluator(&iv, &ctx) { + println!("\x1b[92mCookies contain \";admin=true;\"\x1b[0m"); + println!("\x1b[92;1mAdmin access granted!\"\x1b[0m"); + } else { + println!("\x1b[91;1mStill a guest, better luck next time!\"\x1b[0m"); + } +}