Merge pull request #10 from 0xalivecow/dev

Add padding oracle funcionality
This commit is contained in:
An0nymous 2024-11-07 10:32:32 +01:00 committed by GitHub
commit 5953b98897
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 230 additions and 30 deletions

View file

@ -10,6 +10,7 @@ use tasks01::{
block2poly::block2poly, block2poly::block2poly,
gcm::{gcm_decrypt, gcm_encrypt}, gcm::{gcm_decrypt, gcm_encrypt},
gfmul::gfmul_task, gfmul::gfmul_task,
pad_oracle::padding_oracle,
poly2block::poly2block, poly2block::poly2block,
sea128::sea128, sea128::sea128,
xex::{self, fde_xex}, xex::{self, fde_xex},
@ -75,7 +76,13 @@ pub fn task_deploy(testcase: &Testcase) -> Result<Value> {
Ok(json) 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!( _ => Err(anyhow!(
"Fatal. No compatible action found. Json data was {:?}. Arguments were; {:?}", "Fatal. No compatible action found. Json data was {:?}. Arguments were; {:?}",
testcase, testcase,

View file

@ -108,4 +108,24 @@ mod tests {
); );
Ok(()) 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(())
}
} }

View file

@ -1,6 +1,7 @@
pub mod block2poly; pub mod block2poly;
pub mod gcm; pub mod gcm;
pub mod gfmul; pub mod gfmul;
pub mod pad_oracle;
pub mod poly2block; pub mod poly2block;
pub mod sea128; pub mod sea128;
pub mod xex; pub mod xex;

View file

@ -0,0 +1,167 @@
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<Vec<u8>> {
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<u8> = BASE64_STANDARD.decode(iv_string)?;
let cipher_text: String = serde_json::from_value(args["ciphertext"].clone())?;
let ciphertext: Vec<u8> = 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<u8> = 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<u8> = 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
// TODO: Collect all and send in one
let mut payload: Vec<u8> = vec![];
for j in 0..q_block_count {
// Next byte
//eprintln!("Sending attack block: {:02X?}", attack_counter);
//thread::sleep(Duration::from_millis(1000));
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)?;
//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;
// Check for edgecase
if i == 15 {
let mut check_q_block: Vec<u8> = 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(
cipher_chunks[chunk_counter + 1][i]
^ (attack_counter[i as usize] ^ (15 - i as u8 + 1)),
);
} else {
//seprintln!("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(())
}
}

View file

@ -7,6 +7,9 @@ use openssl::symm::{Cipher, Crypter, Mode};
use super::math::xor_bytes; 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<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn aes_128_encrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
let mut encrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Encrypt, &key, None)?; let mut encrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Encrypt, &key, None)?;
encrypter.pad(false); encrypter.pad(false);
@ -22,6 +25,9 @@ pub fn aes_128_encrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
Ok(ciphertext) 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<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn aes_128_decrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
let mut decrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, key, None)?; let mut decrypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, key, None)?;
decrypter.pad(false); decrypter.pad(false);
@ -39,8 +45,14 @@ pub fn aes_128_decrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
Ok(plaintext) 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<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn sea_128_encrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
// Constant value used for XOR
let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11; let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11;
let sea128_out = xor_bytes( let sea128_out = xor_bytes(
&aes_128_encrypt(key, input)?, &aes_128_encrypt(key, input)?,
xor_val.to_be_bytes().to_vec(), xor_val.to_be_bytes().to_vec(),
@ -48,38 +60,30 @@ pub fn sea_128_encrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
Ok(sea128_out) 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<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn sea_128_decrypt(key: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
// Constant value used for XOR
let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11; let xor_val: u128 = 0xc0ffeec0ffeec0ffeec0ffeec0ffee11;
let intermediate = xor_bytes(input, xor_val.to_be_bytes().to_vec())?; let intermediate = xor_bytes(input, xor_val.to_be_bytes().to_vec())?;
Ok(aes_128_decrypt(&key, &intermediate)?) 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<u8>, tweak: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn xex_encrypt(mut key: Vec<u8>, tweak: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
let key2: Vec<u8> = key.split_off(16); let key2: Vec<u8> = key.split_off(16);
//let key1: ByteArray = ByteArray(vec![key_parts[0]]);
//let key2: ByteArray = ByteArray(vec![key_parts[1]]);
let input_chunks: Vec<Vec<u8>> = input.chunks(16).map(|x| x.to_vec()).collect(); let input_chunks: Vec<Vec<u8>> = input.chunks(16).map(|x| x.to_vec()).collect();
let mut output: Vec<u8> = vec![]; let mut output: Vec<u8> = 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)?); let mut tweak_block: ByteArray = ByteArray(sea_128_encrypt(&key2, tweak)?);
//dbg!("input_chunks: {:001X?}", &input_chunks);
for chunk in input_chunks { for chunk in input_chunks {
let plaintext_intermediate = xor_bytes(&tweak_block.0, chunk)?; 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 cypher_block_intermediate = sea_128_encrypt(&key, &plaintext_intermediate)?;
let mut cypher_block = xor_bytes(&tweak_block.0, cypher_block_intermediate)?; let mut cypher_block = xor_bytes(&tweak_block.0, cypher_block_intermediate)?;
output.append(cypher_block.as_mut()); output.append(cypher_block.as_mut());
@ -91,28 +95,13 @@ pub fn xex_encrypt(mut key: Vec<u8>, tweak: &Vec<u8>, input: &Vec<u8>) -> Result
pub fn xex_decrypt(mut key: Vec<u8>, tweak: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> { pub fn xex_decrypt(mut key: Vec<u8>, tweak: &Vec<u8>, input: &Vec<u8>) -> Result<Vec<u8>> {
let key2: Vec<u8> = key.split_off(16); let key2: Vec<u8> = key.split_off(16);
//let key1: ByteArray = ByteArray(vec![key_parts[0]]);
//let key2: ByteArray = ByteArray(vec![key_parts[1]]);
let input_chunks: Vec<Vec<u8>> = input.chunks(16).map(|x| x.to_vec()).collect(); let input_chunks: Vec<Vec<u8>> = input.chunks(16).map(|x| x.to_vec()).collect();
let mut output: Vec<u8> = vec![]; let mut output: Vec<u8> = 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)?); let mut tweak_block: ByteArray = ByteArray(sea_128_encrypt(&key2, tweak)?);
for chunk in input_chunks { for chunk in input_chunks {
let cyphertext_intermediate = xor_bytes(&tweak_block.0, chunk)?; 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 plaintext_block_intermediate = sea_128_decrypt(&key, &cyphertext_intermediate)?;
let mut cypher_block = xor_bytes(&tweak_block.0, plaintext_block_intermediate)?; let mut cypher_block = xor_bytes(&tweak_block.0, plaintext_block_intermediate)?;
output.append(cypher_block.as_mut()); output.append(cypher_block.as_mut());
@ -136,6 +125,7 @@ pub fn gcm_encrypt_aes(
eprintln!("{:001X?}", nonce); eprintln!("{:001X?}", nonce);
let auth_tag_xor = aes_128_encrypt(&key, &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())?; let auth_key_h = aes_128_encrypt(&key, &0u128.to_be_bytes().to_vec())?;

View file

@ -1,5 +1,6 @@
pub mod ciphers; pub mod ciphers;
pub mod field; pub mod field;
pub mod math; pub mod math;
pub mod net;
pub mod parse; pub mod parse;
pub mod poly; pub mod poly;

1
src/utils/net.rs Normal file
View file

@ -0,0 +1 @@

View file

@ -0,0 +1,13 @@
{
"testcases": {
"254eaee7-05fd-4e0d-8292-9b658a852245": {
"action": "padding_oracle",
"arguments": {
"hostname": "localhost",
"port": 1337,
"iv": "AAAAAAAAAAAAAAAAAAAAAA==",
"ciphertext": "QENCRURHRklIS0pNTE9OUQAAAAAAAAAASUlJSUlJSUk="
}
}
}
}