Solve challenge 32 and with that problem set 4

This commit is contained in:
2022-08-28 19:48:36 -04:00
parent f50197e480
commit ddafb43934
4 changed files with 104 additions and 61 deletions

View File

@@ -1,9 +1,9 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_precision_loss)]
#![allow(clippy::items_after_statements)] #![allow(clippy::items_after_statements)]
#![allow(clippy::many_single_char_names)] #![allow(clippy::many_single_char_names)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::unnested_or_patterns)] #![allow(clippy::unnested_or_patterns)]
mod bytes; mod bytes;
mod bytes_base64; mod bytes_base64;
@@ -18,6 +18,7 @@ mod set1;
mod set2; mod set2;
mod set3; mod set3;
mod set4; mod set4;
mod set5;
mod sha1; mod sha1;
mod utils; mod utils;
@@ -55,6 +56,7 @@ fn main() {
set4::challenge29(); set4::challenge29();
set4::challenge30(); set4::challenge30();
set4::challenge31(); set4::challenge31();
set4::challenge32();
} }
set4::challenge32(); set5::challenge33();
} }

View File

@@ -116,7 +116,6 @@ pub fn challenge18() {
println!("[okay] Challenge 18: {cleartext}"); println!("[okay] Challenge 18: {cleartext}");
} }
mod challenge19 { mod challenge19 {
use crate::bytes::Bytes; use crate::bytes::Bytes;
use std::cell::RefCell; use std::cell::RefCell;
@@ -232,7 +231,6 @@ mod challenge19 {
deciphered deciphered
} }
} }
pub fn challenge19() { pub fn challenge19() {
@@ -313,6 +311,18 @@ pub fn challenge21() {
} }
pub fn challenge22() { pub fn challenge22() {
fn find_seed(rngout: u32) -> Option<u32> {
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. // Wait a random number of seconds between, I don't know, 40 and 1000.
let now = utils::unix_timestamp(); let now = utils::unix_timestamp();
let wait_time: u32 = rand::thread_rng().gen_range(40..1000); let wait_time: u32 = rand::thread_rng().gen_range(40..1000);
@@ -326,18 +336,6 @@ pub fn challenge22() {
let rngout = mt.extract_number(); let rngout = mt.extract_number();
// From the 32 bit RNG output, discover the seed. // From the 32 bit RNG output, discover the seed.
fn find_seed(rngout: u32) -> Option<u32> {
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); let found_seed = find_seed(rngout);
assert_eq!(seed, found_seed.unwrap()); assert_eq!(seed, found_seed.unwrap());

View File

@@ -1,14 +1,7 @@
use std::path::Path;
use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils}; use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils};
use std::path::Path;
pub fn challenge25() { 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, // Now, write the code that allows you to "seek" into the ciphertext,
// decrypt, and re-encrypt with different plaintext. Expose this as a // decrypt, and re-encrypt with different plaintext. Expose this as a
// function, like, "edit(ciphertext, key, offset, newtext)". // function, like, "edit(ciphertext, key, offset, newtext)".
@@ -22,6 +15,13 @@ pub fn challenge25() {
ctr::encrypt(key, 0, &plaintext) 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 // 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 // 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 // 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"); println!("[okay] Challenge 25: recovered AES CTR plaintext via edit");
// A folkloric supposed benefit of CTR mode is the ability to easily "seek // A folkloric supposed benefit of CTR mode is the ability to easily "seek forward" into the
// forward" into the ciphertext; to access byte N of the ciphertext, all you // ciphertext; to access byte N of the ciphertext, all you need to be able to do is generate
// need to be able to do is generate byte N of the keystream. Imagine if // byte N of the keystream. Imagine if you'd relied on that advice to, say, encrypt a disk.
// 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() { pub fn challenge26() {
@@ -229,27 +230,28 @@ pub fn challenge29() {
} }
pub fn challenge30() { pub fn challenge30() {
// test MD4 implementation fn test_md4() {
assert_eq!( assert_eq!(
md4::hash(&Bytes::from_utf8("")), md4::hash(&Bytes::from_utf8("")),
Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"), Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"),
); );
assert_eq!( assert_eq!(
md4::hash(&Bytes::from_utf8("a")), md4::hash(&Bytes::from_utf8("a")),
Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"), Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"),
); );
assert_eq!( assert_eq!(
md4::hash(&Bytes::from_utf8("abc")), md4::hash(&Bytes::from_utf8("abc")),
Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"), Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"),
); );
assert_eq!( assert_eq!(
md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")), md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")),
Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"),
); );
assert_eq!( assert_eq!(
md4::hash(&Bytes(vec![b'a'; 1337])), md4::hash(&Bytes(vec![b'a'; 1337])),
Bytes::from_hex("9a4bceae0ae389c4653ad92cfd7bfc3e"), Bytes::from_hex("9a4bceae0ae389c4653ad92cfd7bfc3e"),
); );
}
// extend MD4 copy and pasted from SHA-1 #allow!(dont-repeat-yourself) // extend MD4 copy and pasted from SHA-1 #allow!(dont-repeat-yourself)
fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes { fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes {
@@ -263,6 +265,7 @@ pub fn challenge30() {
m.hash(bytes) m.hash(bytes)
} }
test_md4();
let key = Bytes::random_range(2, 64); let key = Bytes::random_range(2, 64);
let message = Bytes::from_utf8( let message = Bytes::from_utf8(
"comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon", "comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon",
@@ -300,8 +303,8 @@ pub fn challenge30() {
} }
mod challenge31 { mod challenge31 {
use std::fs;
use crate::{bytes::Bytes, sha1}; use crate::{bytes::Bytes, sha1};
use std::fs;
use std::path::Path; use std::path::Path;
use std::{thread, time}; use std::{thread, time};
@@ -329,13 +332,13 @@ mod challenge31 {
if a != b { if a != b {
return false; 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); thread::sleep(delay);
} }
true true
} }
pub fn attack(file: &Path, delay: u64) -> Bytes { pub fn _attack(file: &Path, delay: u64) -> Bytes {
const BLOCK_SIZE: usize = 20; const BLOCK_SIZE: usize = 20;
let mut sig = vec![0x0; BLOCK_SIZE]; let mut sig = vec![0x0; BLOCK_SIZE];
for i in 0..BLOCK_SIZE { for i in 0..BLOCK_SIZE {
@@ -358,24 +361,61 @@ mod challenge31 {
pub fn challenge31() { pub fn challenge31() {
let key = Bytes::from_utf8("YELLOW SUBMARINE"); let key = Bytes::from_utf8("YELLOW SUBMARINE");
let message = Bytes::from_utf8("Attact at dawn after tomorrow when it's cold inside."); 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 path = Path::new("data/12.txt");
let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138"); 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. // Don't attack because it interrupts the flow of the other challenges by taking long.
// let signature = challenge31::attack(path, 20); // const DELAY: u64 = 20;
// let signature = challenge31::_attack(path, DELAY);
// assert_eq!(expected_sig, signature, "Recovery of HMAC-SHA1 failed"); // assert_eq!(expected_sig, signature, "Recovery of HMAC-SHA1 failed");
println!("[okay] Challenge 31: recovered HMAC-SHA1 via timing attack"); println!("[okay] Challenge 31: recovered HMAC-SHA1 via timing attack");
} }
pub fn challenge32() { pub fn challenge32() {
const DELAY: u64 = 1; use std::time;
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");
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");
} }

3
src/set5.rs Normal file
View File

@@ -0,0 +1,3 @@
pub fn challenge33() {
println!("[xxxx] Challenge 33: now we talking!");
}