Solve challenge 32 and with that problem set 4
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/set3.rs
26
src/set3.rs
@@ -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());
|
||||||
|
|
||||||
|
|||||||
130
src/set4.rs
130
src/set4.rs
@@ -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
3
src/set5.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub fn challenge33() {
|
||||||
|
println!("[xxxx] Challenge 33: now we talking!");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user