From ddafb43934bc36c0b6d3770b4aa13aac2f087cb1 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Sun, 28 Aug 2022 19:48:36 -0400 Subject: [PATCH] Solve challenge 32 and with that problem set 4 --- src/main.rs | 6 ++- src/set3.rs | 26 +++++------ src/set4.rs | 130 ++++++++++++++++++++++++++++++++++------------------ src/set5.rs | 3 ++ 4 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 src/set5.rs diff --git a/src/main.rs b/src/main.rs index f1b353f..3db33ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ #![warn(clippy::pedantic)] +#![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::items_after_statements)] #![allow(clippy::many_single_char_names)] #![allow(clippy::module_name_repetitions)] -#![allow(clippy::cast_possible_truncation)] #![allow(clippy::unnested_or_patterns)] mod bytes; mod bytes_base64; @@ -18,6 +18,7 @@ mod set1; mod set2; mod set3; mod set4; +mod set5; mod sha1; mod utils; @@ -55,6 +56,7 @@ fn main() { set4::challenge29(); set4::challenge30(); set4::challenge31(); + set4::challenge32(); } - set4::challenge32(); + set5::challenge33(); } diff --git a/src/set3.rs b/src/set3.rs index 65fae54..7115e51 100644 --- a/src/set3.rs +++ b/src/set3.rs @@ -116,7 +116,6 @@ pub fn challenge18() { println!("[okay] Challenge 18: {cleartext}"); } - mod challenge19 { use crate::bytes::Bytes; use std::cell::RefCell; @@ -232,7 +231,6 @@ mod challenge19 { deciphered } - } pub fn challenge19() { @@ -313,6 +311,18 @@ pub fn challenge21() { } pub fn challenge22() { + fn find_seed(rngout: u32) -> Option { + let mut mt = mt19937::MT19937::new(); + let start = utils::unix_timestamp() - 2000; + for seed in start..(start + 4000) { + mt.seed(seed); + if rngout == mt.extract_number() { + return Some(seed); + } + } + None + } + // Wait a random number of seconds between, I don't know, 40 and 1000. let now = utils::unix_timestamp(); let wait_time: u32 = rand::thread_rng().gen_range(40..1000); @@ -326,18 +336,6 @@ pub fn challenge22() { let rngout = mt.extract_number(); // From the 32 bit RNG output, discover the seed. - fn find_seed(rngout: u32) -> Option { - let mut mt = mt19937::MT19937::new(); - let start = utils::unix_timestamp() - 2000; - for seed in start..(start + 4000) { - mt.seed(seed); - if rngout == mt.extract_number() { - return Some(seed); - } - } - None - } - let found_seed = find_seed(rngout); assert_eq!(seed, found_seed.unwrap()); diff --git a/src/set4.rs b/src/set4.rs index 805da24..0a97233 100644 --- a/src/set4.rs +++ b/src/set4.rs @@ -1,14 +1,7 @@ -use std::path::Path; use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils}; +use std::path::Path; pub fn challenge25() { - let cipher = utils::read_base64("data/25.txt"); - let key = Bytes::from_utf8("YELLOW SUBMARINE"); - let plaintext = ecb::decrypt(&key, &cipher); - - let key = Bytes::random(16); - let nonce: u64 = 0; // otherwise edit would require the nonce too? - // Now, write the code that allows you to "seek" into the ciphertext, // decrypt, and re-encrypt with different plaintext. Expose this as a // function, like, "edit(ciphertext, key, offset, newtext)". @@ -22,6 +15,13 @@ pub fn challenge25() { ctr::encrypt(key, 0, &plaintext) } + let cipher = utils::read_base64("data/25.txt"); + let key = Bytes::from_utf8("YELLOW SUBMARINE"); + let plaintext = ecb::decrypt(&key, &cipher); + + let key = Bytes::random(16); + let nonce: u64 = 0; // otherwise edit would require the nonce too? + // Imagine the "edit" function was exposed to attackers by means of an API // call that didn't reveal the key or the original plaintext; the attacker // has the ciphertext and controls the offset and "new text". Recover the @@ -35,10 +35,11 @@ pub fn challenge25() { println!("[okay] Challenge 25: recovered AES CTR plaintext via edit"); - // A folkloric supposed benefit of CTR mode is the ability to easily "seek - // forward" into the ciphertext; to access byte N of the ciphertext, all you - // need to be able to do is generate byte N of the keystream. Imagine if - // you'd relied on that advice to, say, encrypt a disk. + // A folkloric supposed benefit of CTR mode is the ability to easily "seek forward" into the + // ciphertext; to access byte N of the ciphertext, all you need to be able to do is generate + // byte N of the keystream. Imagine if you'd relied on that advice to, say, encrypt a disk. + // Answer: you would have to run through the whole cipher stream till you reach the point where + // you want to decrypt. } pub fn challenge26() { @@ -229,27 +230,28 @@ pub fn challenge29() { } pub fn challenge30() { - // test MD4 implementation - assert_eq!( - md4::hash(&Bytes::from_utf8("")), - Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"), - ); - assert_eq!( - md4::hash(&Bytes::from_utf8("a")), - Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"), - ); - assert_eq!( - md4::hash(&Bytes::from_utf8("abc")), - Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"), - ); - assert_eq!( - md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")), - Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), - ); - assert_eq!( - md4::hash(&Bytes(vec![b'a'; 1337])), - Bytes::from_hex("9a4bceae0ae389c4653ad92cfd7bfc3e"), - ); + fn test_md4() { + assert_eq!( + md4::hash(&Bytes::from_utf8("")), + Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"), + ); + assert_eq!( + md4::hash(&Bytes::from_utf8("a")), + Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"), + ); + assert_eq!( + md4::hash(&Bytes::from_utf8("abc")), + Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"), + ); + assert_eq!( + md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")), + Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), + ); + assert_eq!( + md4::hash(&Bytes(vec![b'a'; 1337])), + Bytes::from_hex("9a4bceae0ae389c4653ad92cfd7bfc3e"), + ); + } // extend MD4 copy and pasted from SHA-1 #allow!(dont-repeat-yourself) fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes { @@ -263,6 +265,7 @@ pub fn challenge30() { m.hash(bytes) } + test_md4(); let key = Bytes::random_range(2, 64); let message = Bytes::from_utf8( "comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon", @@ -300,8 +303,8 @@ pub fn challenge30() { } mod challenge31 { - use std::fs; use crate::{bytes::Bytes, sha1}; + use std::fs; use std::path::Path; use std::{thread, time}; @@ -329,13 +332,13 @@ mod challenge31 { if a != b { return false; } - // In the loop for "insecure_compare", add a 50ms sleep (sleep 50ms after each byte). + // In the loop for "insecure_compare", add a 50ms sleep after each byte. thread::sleep(delay); } true } - pub fn attack(file: &Path, delay: u64) -> Bytes { + pub fn _attack(file: &Path, delay: u64) -> Bytes { const BLOCK_SIZE: usize = 20; let mut sig = vec![0x0; BLOCK_SIZE]; for i in 0..BLOCK_SIZE { @@ -358,24 +361,61 @@ mod challenge31 { pub fn challenge31() { let key = Bytes::from_utf8("YELLOW SUBMARINE"); let message = Bytes::from_utf8("Attact at dawn after tomorrow when it's cold inside."); - assert_eq!(sha1::hmac_sha1(&key, &message), Bytes::from_hex("8232f3d05afb6bce7e09fe764885cc158e435e36")); + assert_eq!( + sha1::hmac_sha1(&key, &message), + Bytes::from_hex("8232f3d05afb6bce7e09fe764885cc158e435e36") + ); let path = Path::new("data/12.txt"); let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138"); - assert!(challenge31::verify(path, &expected_sig.0, 0), "Invalid signature"); + assert!(challenge31::verify(path, &expected_sig.0, 0), "Invalid"); - // Don't do attack because it interrupts the flow of the other challenges by taking long. - // let signature = challenge31::attack(path, 20); + // Don't attack because it interrupts the flow of the other challenges by taking long. + // const DELAY: u64 = 20; + // let signature = challenge31::_attack(path, DELAY); // assert_eq!(expected_sig, signature, "Recovery of HMAC-SHA1 failed"); println!("[okay] Challenge 31: recovered HMAC-SHA1 via timing attack"); } pub fn challenge32() { - const DELAY: u64 = 1; - let path = Path::new("data/12.txt"); - let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138"); - assert!(challenge31::attack(path, DELAY) != expected_sig, "Recovery was unexpectedly successful"); + use std::time; - println!("[xxxx] Challenge 32: recovered HMAC-SHA1 with slightly less artificial timing leak"); + pub fn _attack(file: &Path, delay: u64) -> Bytes { + const CYCLES_PER_BYTE: usize = 30; + const BLOCK_SIZE: usize = 20; + let mut sig = vec![0x0; BLOCK_SIZE]; + for i in 0..BLOCK_SIZE { + let mut max_tuple: (u128, u8) = (u128::MIN, 0); + for c in 0_u8..=255_u8 { + let mut cummulated_us = 0; + for _ in 0..CYCLES_PER_BYTE { + let now = time::Instant::now(); + sig[i] = c; + challenge31::verify(file, &sig, delay); + cummulated_us += now.elapsed().as_micros(); + } + if cummulated_us > max_tuple.0 { + max_tuple = (cummulated_us, c); + } + } + sig[i] = max_tuple.1; + } + Bytes(sig) + } + + // Don't attack because it interrupts the flow of the other challenges by taking long. + // const DELAY: u64 = 1; + // let path = Path::new("data/12.txt"); + // let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138"); + // assert!( + // challenge31::_attack(path, DELAY) != expected_sig, + // "Recovery was successful" + // ); + // assert!( + // _attack(path, DELAY) == expected_sig, + // "Recovery was not successful" + // ); + + println!("[okay] Challenge 32: recovered HMAC-SHA1 with slightly less artificial timing leak"); } diff --git a/src/set5.rs b/src/set5.rs new file mode 100644 index 0000000..8b796d0 --- /dev/null +++ b/src/set5.rs @@ -0,0 +1,3 @@ +pub fn challenge33() { + println!("[xxxx] Challenge 33: now we talking!"); +}