diff --git a/src/bytes.rs b/src/bytes.rs index 9e96aa6..587b2ab 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -107,10 +107,13 @@ impl Bytes { } let last_block_index = self.len() / block_size - 1; let last_block = self.get_block(last_block_index, block_size).0; - let pad_byte_count = last_block[block_size - 1]; - for i in 0..(pad_byte_count as usize) { + let pad_byte = last_block[block_size - 1]; + if pad_byte < 1 || pad_byte > 16 { + return false; + } + for i in 0..(pad_byte as usize) { let byte = last_block[block_size - 1 - i]; - if byte != pad_byte_count { + if byte != pad_byte { return false; } } diff --git a/src/main.rs b/src/main.rs index 7a89c86..7534c99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,21 +8,21 @@ mod set2; mod set3; fn main() { - // set1::challenge1(); - // set1::challenge2(); - // set1::challenge3(); - // set1::challenge4(); - // set1::challenge5(); - // set1::challenge6(); - // set1::challenge7(); - // set1::challenge8(); - // set2::challenge9(); - // set2::challenge10(); - // set2::challenge11(); - // set2::challenge12(); - // set2::challenge13(); - // set2::challenge14(); - // set2::challenge15(); - // set2::challenge16(); + set1::challenge1(); + set1::challenge2(); + set1::challenge3(); + set1::challenge4(); + set1::challenge5(); + set1::challenge6(); + set1::challenge7(); + set1::challenge8(); + set2::challenge9(); + set2::challenge10(); + set2::challenge11(); + set2::challenge12(); + set2::challenge13(); + set2::challenge14(); + set2::challenge15(); + set2::challenge16(); set3::challenge17(); } diff --git a/src/set3.rs b/src/set3.rs index 2a43aa6..0d32f3b 100644 --- a/src/set3.rs +++ b/src/set3.rs @@ -13,7 +13,8 @@ pub fn challenge17() { .collect() } - fn encrypt(key: &Bytes) -> (Bytes, Bytes) { + let key = Bytes::random(16); + let encrypt = || -> (Bytes, Bytes, usize) { // The first function should select at random one of the ten strings let cleartexts = read("data/17.txt"); let index: usize = rand::thread_rng().gen_range(0..cleartexts.len()); @@ -23,37 +24,72 @@ pub fn challenge17() { cleartext.pad_pkcs7(16); // CBC-encrypt it under that key, providing the caller the ciphertext - // and IV. + // and IV and cleartext index for check. let iv = Bytes::random(16); - (cbc::encrypt(&key, &iv, &cleartext), iv) - } + (cbc::encrypt(&key, &iv, &cleartext), iv, index) + }; // generate a random AES key (which it should save for all future encryptions) - let key = Bytes::random(16); - let (cipher, _iv) = encrypt(&key); + let (cipher, iv, cleartext_index) = encrypt(); let decryption_oracle = |iv: &Bytes, cipher: &Bytes| -> bool { // The second function should consume the ciphertext produced by the // first function, decrypt it, check its padding, and return true or // false depending on whether the padding is valid. - let roundtrip = cbc::decrypt(&key, iv, cipher); - roundtrip.has_valid_pkcs7(16) + let cleartext = cbc::decrypt(&key, iv, cipher); + cleartext.has_valid_pkcs7(16) }; - let attack_block = |cipher_block: &Bytes| -> Bytes { + let attack_block = |previous_block: &Bytes, cipher_block: &Bytes| -> Bytes { + // Good explanation: https://robertheaton.com/2013/07/29/padding-oracle-attack/ let block_size = cipher_block.len(); - let mut iv = Bytes::random(block_size); + let mut attack_vector = Bytes::random(block_size); + let mut intermittent_result = vec![]; - for i in 0..=255 { - iv.0[15] = i; + for pad_byte in 1..=block_size { + // preset attack vector so that paddinig is [1], [2, 2], [3, 3, 3], and so on. + attack_vector.0[block_size - pad_byte] = pad_byte as u8; + for i in 0..(pad_byte - 1) { + attack_vector.0[block_size - 1 - i] = (pad_byte as u8) ^ intermittent_result[i]; + } - if decryption_oracle(&iv, cipher_block) { - println!("{i:#016b}"); + // guess attack vector so that padding is valid + let guess_index = block_size - pad_byte; + for guess in 0..=255 { + attack_vector.0[guess_index] = guess; + if decryption_oracle(&attack_vector, cipher_block) { + // println!("{guess:#016b}"); + let c = (guess as u8) ^ (pad_byte as u8); + intermittent_result.push(c); + } } } - Bytes(vec![]) + + // transform intermittent result by xoring it with previous block + intermittent_result.reverse(); + let xored: Vec = Iterator::zip(previous_block.0.iter(), intermittent_result) + .map(|z| z.0 ^ z.1) + .collect(); + assert_eq!(xored.len(), block_size); + Bytes(xored) }; - let _block_count = cipher.len() / 16; - let first_block = attack_block(&cipher.get_block(0, 16)); - println!("[xxxx] Challenge 17: {:?}", first_block); + // Attack block by block. + let mut roundtrip = Bytes(vec![]); + let block_count = cipher.len() / 16; + for block in 0..block_count { + let mut clear_block = if block == 0 { + attack_block(&iv, &cipher.get_block(0, 16)) + } else { + attack_block( + &cipher.get_block(block - 1, 16), + &cipher.get_block(block, 16), + ) + }; + roundtrip.0.append(&mut clear_block.0); + } + roundtrip.remove_pkcs7(16); + let cleartexts = read("data/17.txt"); + let cleartext = Bytes(cleartexts[cleartext_index].0.to_vec()); + assert_eq!(roundtrip, cleartext); + println!("[okay] Challenge 17: {}", roundtrip.to_utf8()); }