From b81bbab16c289c9d13a86fc3495c807014ee73cb Mon Sep 17 00:00:00 2001 From: 0xalivecow Date: Mon, 4 Nov 2024 15:46:09 +0100 Subject: [PATCH 1/4] doc: add docmentation and test --- src/tasks/tasks01/gfmul.rs | 20 +++++++++++++++ src/utils/ciphers.rs | 50 +++++++++++++++----------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/tasks/tasks01/gfmul.rs b/src/tasks/tasks01/gfmul.rs index c4acefe..01731f6 100644 --- a/src/tasks/tasks01/gfmul.rs +++ b/src/tasks/tasks01/gfmul.rs @@ -108,4 +108,24 @@ mod tests { ); Ok(()) } + + #[test] + fn gfmul_task01_gcm() -> Result<()> { + let args: Value = json!({"a": "AAAAAAAAAAAAAAAQBAAAAA==", "b": "IAAAAAAAAACAAAAAAAAAAA=="}); + + let poly1_text: String = serde_json::from_value(args["a"].clone())?; + let poly_a = BASE64_STANDARD.decode(poly1_text)?; + + let poly2_text: String = serde_json::from_value(args["b"].clone())?; + let poly_b = BASE64_STANDARD.decode(poly2_text)?; + + let result = BASE64_STANDARD.encode(gfmul(poly_a, poly_b, "gcm")?); + + assert_eq!( + result, "hSQAAAAAAAAAAAAAAAAAAA==", + "Failure. Calulated result was: {}", + result + ); + Ok(()) + } } diff --git a/src/utils/ciphers.rs b/src/utils/ciphers.rs index 56b4ead..917a1c7 100644 --- a/src/utils/ciphers.rs +++ b/src/utils/ciphers.rs @@ -7,6 +7,9 @@ use openssl::symm::{Cipher, Crypter, Mode}; use super::math::xor_bytes; +/// AES ENCRYPT +/// Function to perform encryption with AES ECB mode +/// Function does not use padding for blocks pub fn aes_128_encrypt(key: &Vec, input: &Vec) -> Result> { let mut encrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Encrypt, &key, None)?; encrypter.pad(false); @@ -22,6 +25,9 @@ pub fn aes_128_encrypt(key: &Vec, input: &Vec) -> Result> { Ok(ciphertext) } +/// AES DECRPYT +/// Function to perform decryption with AES ECB mode +/// Function does not use padding for blocks pub fn aes_128_decrypt(key: &Vec, input: &Vec) -> Result> { let mut decrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, key, None)?; decrypter.pad(false); @@ -39,8 +45,14 @@ pub fn aes_128_decrypt(key: &Vec, input: &Vec) -> Result> { Ok(plaintext) } +/// SEA ENCRYPT +/// Function to perform sea encrption. +/// At its core, the function ses the AES ENCRYPT, but then xors with a constant value of: +/// 0xc0ffeec0ffeec0ffeec0ffeec0ffee11 pub fn sea_128_encrypt(key: &Vec, input: &Vec) -> Result> { + // Constant value used for XOR let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11; + let sea128_out = xor_bytes( &aes_128_encrypt(key, input)?, xor_val.to_be_bytes().to_vec(), @@ -48,38 +60,30 @@ pub fn sea_128_encrypt(key: &Vec, input: &Vec) -> Result> { Ok(sea128_out) } +/// SEA DECRYPT +/// Function to perform sea decryption. +/// At its core, the function ses the AES DECRYPT, but then xors with a constant value of: +/// 0xc0ffeec0ffeec0ffeec0ffeec0ffee11 pub fn sea_128_decrypt(key: &Vec, input: &Vec) -> Result> { + // Constant value used for XOR let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11; let intermediate = xor_bytes(input, xor_val.to_be_bytes().to_vec())?; Ok(aes_128_decrypt(&key, &intermediate)?) } +/// Function to perform xex encryption. +/// The function performs the encryption for XEX on the basis of the SEA ENCRYPT. pub fn xex_encrypt(mut key: Vec, tweak: &Vec, input: &Vec) -> Result> { let key2: Vec = key.split_off(16); - //let key1: ByteArray = ByteArray(vec![key_parts[0]]); - //let key2: ByteArray = ByteArray(vec![key_parts[1]]); let input_chunks: Vec> = input.chunks(16).map(|x| x.to_vec()).collect(); let mut output: Vec = vec![]; - //assert!(key.len() % 16 == 0, "Failure: Key len {}", key.len()); - //assert!(key2.len() % 16 == 0, "Failure: Key2 len {}", key2.len()); let mut tweak_block: ByteArray = ByteArray(sea_128_encrypt(&key2, tweak)?); - //dbg!("input_chunks: {:001X?}", &input_chunks); - for chunk in input_chunks { let plaintext_intermediate = xor_bytes(&tweak_block.0, chunk)?; - /* - assert!( - plaintext_intermediate.len() % 16 == 0, - "Failure: plaintext_intermediate len was {}", - plaintext_intermediate.len() - ); - */ - //assert!(key.len() % 16 == 0, "Failure: Key len {}", key.len()); - //assert!(key2.len() % 16 == 0, "Failure: Key2 len {}", key2.len()); let cypher_block_intermediate = sea_128_encrypt(&key, &plaintext_intermediate)?; let mut cypher_block = xor_bytes(&tweak_block.0, cypher_block_intermediate)?; output.append(cypher_block.as_mut()); @@ -91,28 +95,13 @@ pub fn xex_encrypt(mut key: Vec, tweak: &Vec, input: &Vec) -> Result pub fn xex_decrypt(mut key: Vec, tweak: &Vec, input: &Vec) -> Result> { let key2: Vec = key.split_off(16); - //let key1: ByteArray = ByteArray(vec![key_parts[0]]); - //let key2: ByteArray = ByteArray(vec![key_parts[1]]); - let input_chunks: Vec> = input.chunks(16).map(|x| x.to_vec()).collect(); let mut output: Vec = vec![]; - //assert!(key.len() % 16 == 0, "Failure: Key len {}", key.len()); - //assert!(key2.len() % 16 == 0, "Failure: Key2 len {}", key2.len()); let mut tweak_block: ByteArray = ByteArray(sea_128_encrypt(&key2, tweak)?); for chunk in input_chunks { let cyphertext_intermediate = xor_bytes(&tweak_block.0, chunk)?; - - /* - assert!( - cyphertext_intermediate.len() % 16 == 0, - "Failure: plaintext_intermediate len was {}", - cyphertext_intermediate.len() - ); - assert!(key.len() % 16 == 0, "Failure: Key len {}", key.len()); - assert!(key2.len() % 16 == 0, "Failure: Key2 len {}", key2.len()); - */ let plaintext_block_intermediate = sea_128_decrypt(&key, &cyphertext_intermediate)?; let mut cypher_block = xor_bytes(&tweak_block.0, plaintext_block_intermediate)?; output.append(cypher_block.as_mut()); @@ -136,6 +125,7 @@ pub fn gcm_encrypt_aes( eprintln!("{:001X?}", nonce); let auth_tag_xor = aes_128_encrypt(&key, &nonce)?; + eprintln!("Y0 {:001X?}", auth_tag_xor); let auth_key_h = aes_128_encrypt(&key, &0u128.to_be_bytes().to_vec())?; From 9ae53e12fd60d4ab876763ce95f192c40f4fe37d Mon Sep 17 00:00:00 2001 From: 0xalivecow Date: Wed, 6 Nov 2024 23:38:54 +0100 Subject: [PATCH 2/4] feat: Initial padding oracle working. Pending check for special case. The initial padding oracle attack is working. More tests need to be added and there needs to be a check for the special case of the 02 01, 02 02 padding case --- src/tasks/mod.rs | 7 ++ src/tasks/tasks01/mod.rs | 1 + src/tasks/tasks01/pad_oracle.rs | 136 ++++++++++++++++++++++++++++++++ src/utils/mod.rs | 1 + src/utils/net.rs | 1 + test_json/padding_oracle.json | 13 +++ 6 files changed, 159 insertions(+) create mode 100644 src/tasks/tasks01/pad_oracle.rs create mode 100644 src/utils/net.rs create mode 100644 test_json/padding_oracle.json diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 6c003b2..0fa72d6 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -10,6 +10,7 @@ use tasks01::{ block2poly::block2poly, gcm::{gcm_decrypt, gcm_encrypt}, gfmul::gfmul_task, + pad_oracle::padding_oracle, poly2block::poly2block, sea128::sea128, xex::{self, fde_xex}, @@ -75,7 +76,13 @@ pub fn task_deploy(testcase: &Testcase) -> Result { Ok(json) } + "padding_oracle" => { + let plaintext = padding_oracle(args)?; + let out_plain = BASE64_STANDARD.encode(&plaintext); + let json = json!({"plaintext" : out_plain}); + Ok(json) + } _ => Err(anyhow!( "Fatal. No compatible action found. Json data was {:?}. Arguments were; {:?}", testcase, diff --git a/src/tasks/tasks01/mod.rs b/src/tasks/tasks01/mod.rs index d1f3e99..479fe47 100644 --- a/src/tasks/tasks01/mod.rs +++ b/src/tasks/tasks01/mod.rs @@ -1,6 +1,7 @@ pub mod block2poly; pub mod gcm; pub mod gfmul; +pub mod pad_oracle; pub mod poly2block; pub mod sea128; pub mod xex; diff --git a/src/tasks/tasks01/pad_oracle.rs b/src/tasks/tasks01/pad_oracle.rs new file mode 100644 index 0000000..31b739c --- /dev/null +++ b/src/tasks/tasks01/pad_oracle.rs @@ -0,0 +1,136 @@ +use anyhow::Result; +use base64::prelude::*; +use serde_json::Value; +use std::io::prelude::*; +use std::net::TcpStream; +use std::time::Duration; +use std::{thread, usize}; + +pub fn padding_oracle(args: &Value) -> Result> { + let hostname: String = serde_json::from_value(args["hostname"].clone())?; + + let port_val: Value = serde_json::from_value(args["port"].clone())?; + let port: u64 = port_val.as_u64().expect("Failure in parsing port number"); + + let iv_string: String = serde_json::from_value(args["iv"].clone())?; + let iv: Vec = BASE64_STANDARD.decode(iv_string)?; + + let cipher_text: String = serde_json::from_value(args["ciphertext"].clone())?; + let ciphertext: Vec = BASE64_STANDARD.decode(cipher_text)?; + + // Initialise tracker to adapt correct byte + let byte_counter = 15; + eprintln!("byte_counter is: {}", byte_counter); + + let mut plaintext: Vec = vec![]; + eprintln!("Ciphertext: {:002X?}", ciphertext); + + let cipher_chunks: Vec<&[u8]> = ciphertext.chunks(16).rev().collect(); + let mut chunk_counter = 0; + + for chunk in &cipher_chunks { + let mut stream = TcpStream::connect(format!("{}:{}", hostname, port))?; + stream.set_nonblocking(false)?; + + // Track value sent to server + let mut attack_counter: Vec = vec![0; 16]; + + // Amount of q blocks to send to server. + // TODO:: May be increased via function + let q_block_count: u16 = 255; + + //Send the first ciphertext chunk + eprintln!("Sending Ciphertext chunk: {:002X?}", chunk); + stream.flush()?; + stream.write_all(&chunk)?; + stream.flush()?; + + for i in (0..=15).rev() { + // Craft length message + // FIXME: Assignment is redundant for now + // TODO: Goal is to maybe add speed increase in the future + let l_msg: [u8; 2] = q_block_count.to_le_bytes(); + //eprintln!("Sending l_msg: {:02X?}", l_msg); + stream.write_all(&l_msg)?; + stream.flush()?; + //eprintln!("L_msg sent"); + + // Generate attack blocks + for j in 0..q_block_count { + // Next byte + //eprintln!("Sending attack block: {:02X?}", attack_counter); + + //thread::sleep(Duration::from_millis(1000)); + stream.write_all(&attack_counter)?; + stream.flush()?; + attack_counter[i as usize] += 1; + } + + // Read server response + let mut buf = [0u8; 0xFF]; + stream.read_exact(&mut buf)?; + //eprintln!("{:02X?}", buf); + + // extract valid position + let valid_val = buf.iter().position(|&r| r == 0x01).expect("No valid found") as u8; + //eprintln!("Valid value found: {:02X?}", valid_val); + + // Craft next attack vector padding; 0x01, 0x02, ... + attack_counter[i as usize] = valid_val; + + if chunk_counter + 1 < cipher_chunks.len() { + eprintln!("XOR Next Ciph block"); + plaintext.push( + cipher_chunks[chunk_counter + 1][i] + ^ (attack_counter[i as usize] ^ (15 - i as u8 + 1)), + ); + } else { + eprintln!("XOR IV"); + + plaintext.push(iv[i] ^ (attack_counter[i as usize] ^ (15 - i as u8 + 1))); + } + //eprintln!("Attack counter after set: {:02X?}", attack_counter); + for pos in i..=15 { + //eprintln!("i is: {:02X?}", i); + //eprintln!("i + 1 is: {:02X?}", ((16 - i) as u8).to_le()); + /* + eprintln!( + "attack_counter[pos as usize]: {:02X?}", + attack_counter[pos as usize] + ); + eprintln!( + "attack_counter[pos as usize] ^ 0x02 {:02X?}", + attack_counter[pos as usize] ^ (15 - i as u8 + 1) + ); + */ + let intermediate = attack_counter[pos as usize] ^ (15 - i as u8 + 1); + + attack_counter[pos as usize] = intermediate ^ ((15 - i as u8 + 1) + 1); + } + stream.flush()?; + + // Write plaintext + //eprintln!("{:02X?}", plaintext); + } + chunk_counter += 1; + stream.flush()?; + // break; + drop(stream); + } + + plaintext.reverse(); + + eprintln!("{:02X?}", BASE64_STANDARD.encode(&plaintext)); + Ok(plaintext) +} // the stream is closed here + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_connection() -> Result<()> { + Ok(()) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2f12ed4..298415b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod ciphers; pub mod field; pub mod math; +pub mod net; pub mod parse; pub mod poly; diff --git a/src/utils/net.rs b/src/utils/net.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/utils/net.rs @@ -0,0 +1 @@ + diff --git a/test_json/padding_oracle.json b/test_json/padding_oracle.json new file mode 100644 index 0000000..c837963 --- /dev/null +++ b/test_json/padding_oracle.json @@ -0,0 +1,13 @@ +{ + "testcases": { + "254eaee7-05fd-4e0d-8292-9b658a852245": { + "action": "padding_oracle", + "arguments": { + "hostname": "localhost", + "port": 1337, + "iv": "AAAAAAAAAAAAAAAAAAAAAA==", + "ciphertext": "QENCRURHRklIS0pNTE9OUQAAAAAAAAAASUlJSUlJSUk=" + } + } + } +} From 757afbdc959cf60efb21a2958749bc21673a08e3 Mon Sep 17 00:00:00 2001 From: 0xalivecow Date: Thu, 7 Nov 2024 09:32:18 +0100 Subject: [PATCH 3/4] refactor: Hopefully increase speed by reducing send code --- src/tasks/tasks01/pad_oracle.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tasks/tasks01/pad_oracle.rs b/src/tasks/tasks01/pad_oracle.rs index 31b739c..35db6fd 100644 --- a/src/tasks/tasks01/pad_oracle.rs +++ b/src/tasks/tasks01/pad_oracle.rs @@ -40,7 +40,7 @@ pub fn padding_oracle(args: &Value) -> Result> { let q_block_count: u16 = 255; //Send the first ciphertext chunk - eprintln!("Sending Ciphertext chunk: {:002X?}", chunk); + //eprintln!("Sending Ciphertext chunk: {:002X?}", chunk); stream.flush()?; stream.write_all(&chunk)?; stream.flush()?; @@ -56,16 +56,20 @@ pub fn padding_oracle(args: &Value) -> Result> { //eprintln!("L_msg sent"); // Generate attack blocks + // TODO: Collect all and send in one + let mut payload: Vec = vec![]; for j in 0..q_block_count { // Next byte //eprintln!("Sending attack block: {:02X?}", attack_counter); //thread::sleep(Duration::from_millis(1000)); - stream.write_all(&attack_counter)?; - stream.flush()?; + payload.append(attack_counter.clone().as_mut()); attack_counter[i as usize] += 1; } + stream.write_all(&payload)?; + stream.flush()?; + // Read server response let mut buf = [0u8; 0xFF]; stream.read_exact(&mut buf)?; @@ -73,19 +77,19 @@ pub fn padding_oracle(args: &Value) -> Result> { // extract valid position let valid_val = buf.iter().position(|&r| r == 0x01).expect("No valid found") as u8; - //eprintln!("Valid value found: {:02X?}", valid_val); + eprintln!("Valid value found: {:02X?}", valid_val); // Craft next attack vector padding; 0x01, 0x02, ... attack_counter[i as usize] = valid_val; if chunk_counter + 1 < cipher_chunks.len() { - eprintln!("XOR Next Ciph block"); + //eprintln!("XOR Next Ciph block"); plaintext.push( cipher_chunks[chunk_counter + 1][i] ^ (attack_counter[i as usize] ^ (15 - i as u8 + 1)), ); } else { - eprintln!("XOR IV"); + //seprintln!("XOR IV"); plaintext.push(iv[i] ^ (attack_counter[i as usize] ^ (15 - i as u8 + 1))); } From 0f8d202a06fef902012a410cdef639b0c78e87ec Mon Sep 17 00:00:00 2001 From: 0xalivecow Date: Thu, 7 Nov 2024 10:28:09 +0100 Subject: [PATCH 4/4] feat: Add edge case treatment --- src/tasks/tasks01/pad_oracle.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/tasks/tasks01/pad_oracle.rs b/src/tasks/tasks01/pad_oracle.rs index 35db6fd..1215bd7 100644 --- a/src/tasks/tasks01/pad_oracle.rs +++ b/src/tasks/tasks01/pad_oracle.rs @@ -77,11 +77,37 @@ pub fn padding_oracle(args: &Value) -> Result> { // extract valid position let valid_val = buf.iter().position(|&r| r == 0x01).expect("No valid found") as u8; - eprintln!("Valid value found: {:02X?}", valid_val); - + //eprintln!("Valid value found: {:02X?}", valid_val); // Craft next attack vector padding; 0x01, 0x02, ... attack_counter[i as usize] = valid_val; + // Check for edgecase + if i == 15 { + let mut check_q_block: Vec = vec![0; 16]; + check_q_block[15] = attack_counter[15] ^ (15 - i as u8); + check_q_block[14] = !check_q_block[15]; + + stream.write_all(&[0x01, 0x00])?; + stream.write_all(&check_q_block)?; + let mut buf = [0u8; 0x01]; + stream.read(&mut buf)?; + + if buf == [0x01] { + eprintln!("Valid padding"); + } else { + eprintln!("Invalid padding"); + // Search for second hit + let valid_val = buf + .iter() + .rev() + .position(|&r| r == 0x01) + .expect("No valid found") as u8; + eprintln!("Valid value found: {:02X?}", valid_val); + // Craft next attack vector padding; 0x01, 0x02, ... + attack_counter[i as usize] = valid_val; + } + } + if chunk_counter + 1 < cipher_chunks.len() { //eprintln!("XOR Next Ciph block"); plaintext.push( @@ -111,6 +137,7 @@ pub fn padding_oracle(args: &Value) -> Result> { attack_counter[pos as usize] = intermediate ^ ((15 - i as u8 + 1) + 1); } + stream.flush()?; // Write plaintext