Clean up and lay groundwork for challange 32
parent
75d4b97524
commit
f50197e480
|
@ -19,10 +19,6 @@ impl Bytes {
|
|||
String::from(std::str::from_utf8(v).unwrap())
|
||||
}
|
||||
|
||||
pub fn to_sub_utf8(&self, length: usize) -> String {
|
||||
Bytes(self.0[..length].to_vec()).to_utf8()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ fn main() {
|
|||
set4::challenge28();
|
||||
set4::challenge29();
|
||||
set4::challenge30();
|
||||
set4::challenge31();
|
||||
}
|
||||
set4::challenge31();
|
||||
set4::challenge32();
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ pub fn challenge5() {
|
|||
let enc = Bytes::xor_cycle(&msg, &key);
|
||||
let exp = Bytes::from_hex("0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f");
|
||||
if enc == exp {
|
||||
println!("[okay] Challenge 5: {}", enc.to_hex());
|
||||
println!("[okay] Challenge 5: {}...", &enc.to_hex()[..50]);
|
||||
} else {
|
||||
println!("[fail] Challenge 5");
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ pub fn challenge12() {
|
|||
// byte, encrypt it, and then compare it to the result of the encryption
|
||||
// oracle, but this approach is fine too.
|
||||
assert_eq!(roundtrip_text.0[..138], cleartext.0);
|
||||
println!("[okay] Challenge 12: {}", roundtrip_text.to_sub_utf8(17));
|
||||
println!("[okay] Challenge 12: {}", &roundtrip_text.to_utf8()[..17]);
|
||||
}
|
||||
|
||||
pub fn challenge13() {
|
||||
|
@ -357,7 +357,7 @@ pub fn challenge14() {
|
|||
let roundtrip_text = decode(&prefix, &key);
|
||||
let cleartext = utils::read_base64("data/12.txt");
|
||||
assert_eq!(roundtrip_text, cleartext);
|
||||
println!("[okay] Challenge 14: {}", roundtrip_text.to_sub_utf8(17));
|
||||
println!("[okay] Challenge 14: {}", &roundtrip_text.to_utf8()[..17]);
|
||||
}
|
||||
|
||||
pub fn challenge15() {
|
||||
|
|
129
src/set3.rs
129
src/set3.rs
|
@ -7,8 +7,6 @@ use crate::mtcipher;
|
|||
use crate::utils;
|
||||
use rand::Rng;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn challenge17() {
|
||||
let key = Bytes::random(16);
|
||||
|
@ -118,7 +116,13 @@ pub fn challenge18() {
|
|||
println!("[okay] Challenge 18: {cleartext}");
|
||||
}
|
||||
|
||||
fn challenge19_attack(ciphers: &[Bytes]) -> Vec<RefCell<Vec<u8>>> {
|
||||
|
||||
mod challenge19 {
|
||||
use crate::bytes::Bytes;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn xor_to_char_set(letters: &Vec<u8>) -> HashMap<u8, RefCell<HashSet<u8>>> {
|
||||
let mut h = HashMap::new();
|
||||
for i in 0..255_u8 {
|
||||
|
@ -152,80 +156,83 @@ fn challenge19_attack(ciphers: &[Bytes]) -> Vec<RefCell<Vec<u8>>> {
|
|||
letters
|
||||
}
|
||||
|
||||
let ciphers_len = ciphers.len();
|
||||
let deciphered = vec![RefCell::new(vec![]); ciphers_len];
|
||||
let max_cipher_len = ciphers.iter().map(Bytes::len).max().unwrap_or(0);
|
||||
pub fn attack(ciphers: &[Bytes]) -> Vec<RefCell<Vec<u8>>> {
|
||||
let ciphers_len = ciphers.len();
|
||||
let deciphered = vec![RefCell::new(vec![]); ciphers_len];
|
||||
let max_cipher_len = ciphers.iter().map(Bytes::len).max().unwrap_or(0);
|
||||
|
||||
for byte_index in 0..max_cipher_len {
|
||||
let letters = match byte_index {
|
||||
// chars that work for 10 and 20 found via trial and error
|
||||
10 => ascii_letters(" _-.,;:'"),
|
||||
20 => ascii_letters(" _-.,;:?"),
|
||||
_ => ascii_letters(" _-.,;:"),
|
||||
};
|
||||
let lookup = xor_to_char_set(&letters);
|
||||
for byte_index in 0..max_cipher_len {
|
||||
let letters = match byte_index {
|
||||
// chars that work for 10 and 20 found via trial and error
|
||||
10 => ascii_letters(" _-.,;:'"),
|
||||
20 => ascii_letters(" _-.,;:?"),
|
||||
_ => ascii_letters(" _-.,;:"),
|
||||
};
|
||||
let lookup = xor_to_char_set(&letters);
|
||||
|
||||
let target_bytes: Vec<Option<u8>> = ciphers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if c.len() > byte_index {
|
||||
Some(c.0[byte_index])
|
||||
} else {
|
||||
None
|
||||
let target_bytes: Vec<Option<u8>> = ciphers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if c.len() > byte_index {
|
||||
Some(c.0[byte_index])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut possible_chars: Vec<HashSet<u8>> = ciphers
|
||||
.iter()
|
||||
.map(|_| letters.iter().copied().collect())
|
||||
.collect();
|
||||
|
||||
for i in 0..ciphers_len {
|
||||
for j in i..ciphers_len {
|
||||
if target_bytes[i] == None || target_bytes[j] == None {
|
||||
continue;
|
||||
}
|
||||
let xored = target_bytes[i].unwrap() ^ target_bytes[j].unwrap();
|
||||
let chars = lookup.get(&xored).unwrap().borrow();
|
||||
possible_chars[i] = possible_chars[i].intersection(&chars).copied().collect();
|
||||
possible_chars[j] = possible_chars[j].intersection(&chars).copied().collect();
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut possible_chars: Vec<HashSet<u8>> = ciphers
|
||||
.iter()
|
||||
.map(|_| letters.iter().copied().collect())
|
||||
.collect();
|
||||
}
|
||||
|
||||
for i in 0..ciphers_len {
|
||||
for j in i..ciphers_len {
|
||||
if target_bytes[i] == None || target_bytes[j] == None {
|
||||
for cipher_index in 0..ciphers_len {
|
||||
if ciphers[cipher_index].len() <= byte_index {
|
||||
continue;
|
||||
}
|
||||
let xored = target_bytes[i].unwrap() ^ target_bytes[j].unwrap();
|
||||
let chars = lookup.get(&xored).unwrap().borrow();
|
||||
possible_chars[i] = possible_chars[i].intersection(&chars).copied().collect();
|
||||
possible_chars[j] = possible_chars[j].intersection(&chars).copied().collect();
|
||||
}
|
||||
}
|
||||
|
||||
for cipher_index in 0..ciphers_len {
|
||||
if ciphers[cipher_index].len() <= byte_index {
|
||||
continue;
|
||||
}
|
||||
let chars: Vec<u8> = possible_chars[cipher_index].iter().copied().collect();
|
||||
match chars.len() {
|
||||
0 => {
|
||||
// println!("No chars for {cipher_index} {byte_index}");
|
||||
deciphered[cipher_index].borrow_mut().push(b'?');
|
||||
}
|
||||
1 => {
|
||||
deciphered[cipher_index]
|
||||
.borrow_mut()
|
||||
.push(u8_lower(chars[0]));
|
||||
}
|
||||
2 => {
|
||||
if u8_lower(chars[0]) == u8_lower(chars[1]) {
|
||||
let chars: Vec<u8> = possible_chars[cipher_index].iter().copied().collect();
|
||||
match chars.len() {
|
||||
0 => {
|
||||
// println!("No chars for {cipher_index} {byte_index}");
|
||||
deciphered[cipher_index].borrow_mut().push(b'?');
|
||||
}
|
||||
1 => {
|
||||
deciphered[cipher_index]
|
||||
.borrow_mut()
|
||||
.push(u8_lower(chars[0]));
|
||||
} else {
|
||||
}
|
||||
2 => {
|
||||
if u8_lower(chars[0]) == u8_lower(chars[1]) {
|
||||
deciphered[cipher_index]
|
||||
.borrow_mut()
|
||||
.push(u8_lower(chars[0]));
|
||||
} else {
|
||||
// println!("Two {chars:?} {cipher_index} {byte_index}");
|
||||
deciphered[cipher_index].borrow_mut().push(b'^');
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// println!("Two {chars:?} {cipher_index} {byte_index}");
|
||||
deciphered[cipher_index].borrow_mut().push(b'^');
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// println!("Two {chars:?} {cipher_index} {byte_index}");
|
||||
deciphered[cipher_index].borrow_mut().push(b'^');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deciphered
|
||||
}
|
||||
|
||||
deciphered
|
||||
}
|
||||
|
||||
pub fn challenge19() {
|
||||
|
@ -252,7 +259,7 @@ pub fn challenge19() {
|
|||
let key = Bytes::from_utf8("YELLOW SUBMARINE");
|
||||
let encrypt = |plaintext: &Bytes| -> Bytes { ctr::encrypt(&key, 0, plaintext) };
|
||||
let ciphers: Vec<Bytes> = plaintexts.iter().map(encrypt).collect();
|
||||
let decrypts = challenge19_attack(&ciphers);
|
||||
let decrypts = challenge19::attack(&ciphers);
|
||||
manual(&decrypts);
|
||||
let first_line = Bytes(decrypts[0].borrow().to_vec()).to_utf8();
|
||||
println!("[okay] Challenge 19: {first_line}");
|
||||
|
|
61
src/set4.rs
61
src/set4.rs
|
@ -1,6 +1,4 @@
|
|||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::{thread, time};
|
||||
use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils};
|
||||
|
||||
pub fn challenge25() {
|
||||
|
@ -301,12 +299,29 @@ pub fn challenge30() {
|
|||
println!("[okay] Challenge 30: implemented and extended MD4 successfully");
|
||||
}
|
||||
|
||||
pub fn challenge31() {
|
||||
fn insecure_compare(a: &[u8], b: &[u8]) -> bool {
|
||||
mod challenge31 {
|
||||
use std::fs;
|
||||
use crate::{bytes::Bytes, sha1};
|
||||
use std::path::Path;
|
||||
use std::{thread, time};
|
||||
|
||||
pub fn verify(file: &Path, signature: &[u8], delay: u64) -> bool {
|
||||
// Have the server generate an HMAC key, and then verify that the "signature" on incoming
|
||||
// requests is valid for "file", using the "==" operator to compare the valid MAC for a
|
||||
// file with the "signature" parameter (in other words, verify the HMAC the way any normal
|
||||
// programmer would verify it).
|
||||
let key = Bytes::from_utf8("sosecretbb");
|
||||
let contents = fs::read_to_string(file);
|
||||
assert!(contents.is_ok(), "Could not read: {}", file.display());
|
||||
let contents = Bytes(contents.unwrap().as_bytes().to_vec());
|
||||
insecure_compare(&sha1::hmac_sha1(&key, &contents).0, signature, delay)
|
||||
}
|
||||
|
||||
fn insecure_compare(a: &[u8], b: &[u8], delay: u64) -> bool {
|
||||
// Write a function, call it "insecure_compare", that implements the == operation by doing
|
||||
// byte-at-a-time comparisons with early exit (ie, return false at the first non-matching
|
||||
// byte).
|
||||
let delay = time::Duration::from_millis(8);
|
||||
let delay = time::Duration::from_millis(delay);
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
|
@ -320,19 +335,7 @@ pub fn challenge31() {
|
|||
true
|
||||
}
|
||||
|
||||
fn verify(file: &Path, signature: &[u8]) -> bool {
|
||||
// Have the server generate an HMAC key, and then verify that the "signature" on incoming
|
||||
// requests is valid for "file", using the "==" operator to compare the valid MAC for a
|
||||
// file with the "signature" parameter (in other words, verify the HMAC the way any normal
|
||||
// programmer would verify it).
|
||||
let key = Bytes::from_utf8("sosecretbb");
|
||||
let contents = fs::read_to_string(file);
|
||||
assert!(contents.is_ok(), "Could not read: {}", file.display());
|
||||
let contents = Bytes(contents.unwrap().as_bytes().to_vec());
|
||||
insecure_compare(&sha1::hmac_sha1(&key, &contents).0, signature)
|
||||
}
|
||||
|
||||
fn attack(file: &Path) -> 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 {
|
||||
|
@ -340,7 +343,7 @@ pub fn challenge31() {
|
|||
for c in 0_u8..=255_u8 {
|
||||
let now = time::Instant::now();
|
||||
sig[i] = c;
|
||||
verify(file, &sig);
|
||||
verify(file, &sig, delay);
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
if elapsed > max_tuple.0 {
|
||||
max_tuple = (elapsed, c);
|
||||
|
@ -350,17 +353,29 @@ pub fn challenge31() {
|
|||
}
|
||||
Bytes(sig)
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
let path = Path::new("data/12.txt");
|
||||
let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138");
|
||||
assert!(verify(path, &expected_sig.0), "Invalid signature");
|
||||
assert!(challenge31::verify(path, &expected_sig.0, 0), "Invalid signature");
|
||||
|
||||
let signature = attack(path);
|
||||
assert_eq!(expected_sig.0, signature.0, "Recovery of HMAC-SHA1 failed");
|
||||
// Don't do attack because it interrupts the flow of the other challenges by taking long.
|
||||
// let signature = challenge31::attack(path, 20);
|
||||
// assert_eq!(expected_sig, signature, "Recovery of HMAC-SHA1 failed");
|
||||
|
||||
println!("[okay] Challenge 31: recoverd HMAC-SHA1 via timing attack");
|
||||
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");
|
||||
|
||||
println!("[xxxx] Challenge 32: recovered HMAC-SHA1 with slightly less artificial timing leak");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue