From: Ummon Date: Wed, 5 Nov 2014 16:27:52 +0000 (+0100) Subject: * Oracle machine finished. X-Git-Url: http://git.euphorik.ch/?p=crypto_lab1.git;a=commitdiff_plain;h=ed4d8f3e7e028bf645089edb775af84e6c3f7bd4 * Oracle machine finished. * Add a fixed version of the protocol. --- diff --git a/lab1_rust/run_attack.sh b/lab1_rust/run_attack.sh new file mode 100755 index 0000000..2850e68 --- /dev/null +++ b/lab1_rust/run_attack.sh @@ -0,0 +1 @@ +time cargo run --release -- oracle-weak diff --git a/lab1_rust/src/end_point.rs b/lab1_rust/src/end_point.rs index 2d0a66c..cb67226 100644 --- a/lab1_rust/src/end_point.rs +++ b/lab1_rust/src/end_point.rs @@ -17,11 +17,12 @@ pub struct Client { pub struct EndPoint { socket: TcpStream, - current_timestamp: u64 + current_timestamp: u64, + variant: packet::Variant, } impl Server { - pub fn new(interface: &str, port: u16) -> IoResult { + pub fn new(interface: &str, port: u16, variant: packet::Variant) -> IoResult { let mut acceptor = try!(TcpListener::bind(interface, port).listen()); let server = Server { @@ -33,7 +34,7 @@ impl Server { for stream in acceptor.incoming() { match stream { Ok(stream) => spawn(proc() { - Server::handle_client(EndPoint::new(stream)); + Server::handle_client(EndPoint::new(stream, variant)); }), _ => return } @@ -52,32 +53,33 @@ impl Server { loop { match end_point.read() { Ok(packet@Packet { t: Command(..), .. }) => { - println!("[Server] Valid command received: {}", packet); + end_point.print("Server", format!("Valid command received: {}", packet)); let answer = Answer(Packet::random_packet_data([])); match end_point.send(answer.clone()) { Ok(_) => - println!("[Server] Answer sent: {}", answer), + end_point.print("Server", format!("Answer sent: {}", answer)), Err(e) => - println!("[Server] Can't send the answer. Error: {}", e) + end_point.print("Server", format!("Can't send the answer. Error: {}", e)) } }, // Socket has been closed. Err(packet::IOReadError(IoError { kind: EndOfFile, .. })) => { - println!("[Server] Connection closed: EOF"); + end_point.print("Server", format!("Connection closed: EOF")); return }, other => - println!("[Server] Error or invalid packet: {}", other) + end_point.print("Server", format!("Error or invalid packet: {}", other)) } } } } impl Client { - pub fn new(address: &str, port: u16) -> IoResult { + pub fn new(address: &str, port: u16, variant: packet::Variant) -> IoResult { Ok(Client { end_point: EndPoint { socket: try!(TcpStream::connect(address, port)), - current_timestamp: 0 + current_timestamp: 0, + variant: variant, }}) } @@ -85,31 +87,41 @@ impl Client { self.end_point.close() } - pub fn send(&mut self, packet: PacketType) { + pub fn send(&mut self, packet: PacketType) -> bool { match packet { Command(_) => { - println!("[Client] Sending: {}", packet); + self.end_point.print("Client", format!("Sending: {}", packet)); match self.end_point.send_with_result(packet) { - Ok(Ok(packet@Packet { t: Answer(..), .. })) => - println!("[Client] Command transmitted correctly, answer: {}", packet), - Ok(Ok(packet)) => - println!("[Client] Command transmitted correctly, wrong answer: {}", packet), - Ok(Err(e)) => - println!("[Client] Answer error: {}", e), - Err(e) => - println!("[Client] Can't send the packet. Error: {}", e) + Ok(Ok(packet@Packet { t: Answer(..), .. })) => { + self.end_point.print("Client", format!("Command transmitted correctly, answer: {}", packet)); + true + }, + Ok(Ok(packet)) => { + self.end_point.print("Client", format!("Command transmitted correctly, wrong answer: {}", packet)); + false + } + Ok(Err(e)) => { + self.end_point.print("Client", format!("Answer error: {}", e)); + false + } + Err(e) => { + self.end_point.print("Client", format!("Can't send the packet. Error: {}", e)); + false + } } }, - other => - println!("[Client] Cannot send this type of packet: {}", other) + other => { + self.end_point.print("Client", format!("Cannot send this type of packet: {}", other)); + false + } } } /// Send some valid and not-valid packets to the server and check the result. /// For each test a new client is created. - pub fn start_tests(address: &str, port: u16) { + pub fn start_tests(address: &str, port: u16, variant: packet::Variant) { let execute = |f: &mut |&mut Client| -> bool| -> bool { - match Client::new(address, port) { + match Client::new(address, port, variant) { Ok(ref mut client) => (*f)(client), Err(e) => { println!("Unable to create a client. Error: {}", e); @@ -120,7 +132,7 @@ impl Client { fn raw_packet(timestamp: u64) -> Vec { let mut m = MemWriter::new(); - match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m) { + match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m, packet::Weak) { Err(_) => vec!(), _ => m.unwrap() } @@ -130,13 +142,7 @@ impl Client { // 1) |client: &mut Client| -> bool { println!("Sending a valid packet..."); - match client.end_point.send_with_result(Command(Packet::random_packet_data([42]))) { - Ok(Ok(Packet { t: Answer(..), .. })) => true, - other => { - println!("Error: {}", other); - false - } - } + client.send(Command(Packet::random_packet_data([42]))) }, // 2) |client: &mut Client| -> bool { @@ -211,7 +217,7 @@ impl Client { println!("Sending a packet with wrong padding (all 0)..."); client.end_point.current_timestamp += 1; let mut m = MemWriter::new(); - let raw_packet = match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: client.end_point.current_timestamp }).write_with_padding_fun(&mut m, |_, _| -> u8 { 0 }) { + let raw_packet = match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: client.end_point.current_timestamp }).write_with_padding_fun(&mut m, packet::Weak, |_, _| -> u8 { 0 }) { Err(_) => vec!(), _ => m.unwrap() }; @@ -246,8 +252,9 @@ impl Client { } impl EndPoint { - pub fn new(socket: TcpStream) -> EndPoint { - EndPoint { socket: socket, current_timestamp: 0 } + pub fn new(mut socket: TcpStream, variant: packet::Variant) -> EndPoint { + let _ = socket.set_nodelay(true); + EndPoint { socket: socket, current_timestamp: 0 , variant: variant} } fn close(&mut self) -> IoResult<()> { @@ -256,6 +263,10 @@ impl EndPoint { Ok(()) } + fn print(&self, prefix: &str, s: String) { + println!("[{}] time: {}. {}", prefix, self.current_timestamp, s); + } + /// Send a packet and wait for an answer synchronously. fn send_with_result(&mut self, p: PacketType) -> IoResult { match self.send(p) { @@ -266,7 +277,7 @@ impl EndPoint { /// Send arbitrary data and wait for an answer synchronously. /// Do not increment the current timestamp. - fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult { + pub fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult { self.socket.set_timeout(DEFAULT_TIMEOUT); match self.socket.write(p) { Err(e) => Err(e), @@ -277,7 +288,7 @@ impl EndPoint { fn send(&mut self, p: PacketType) -> IoResult<()> { self.socket.set_timeout(DEFAULT_TIMEOUT); self.current_timestamp += 1; - match (Packet { t: p, timestamp: self.current_timestamp }).write(&mut self.socket) { + match (Packet { t: p, timestamp: self.current_timestamp }).write(&mut self.socket, self.variant) { Err(packet::WriteIOError(e)) => Err(e), _ => Ok(()) } @@ -292,7 +303,7 @@ impl EndPoint { }; self.socket.set_timeout(DEFAULT_TIMEOUT); - match Packet::read(&mut self.socket) { + match Packet::read(&mut self.socket, self.variant) { Ok(packet) => { if packet.timestamp <= self.current_timestamp { println!("Error, timestamp mismatch, current timestamp: {}, packet received: {}", self.current_timestamp, packet); diff --git a/lab1_rust/src/main.rs b/lab1_rust/src/main.rs index 64aef28..0b14951 100644 --- a/lab1_rust/src/main.rs +++ b/lab1_rust/src/main.rs @@ -17,7 +17,7 @@ const PORT: u16 = 4221; fn print_usage() { println!( - r"{} genkey | tests | oracle-weak | oracle-fixed + r"{} [genkey | tests | oracle-weak | oracle-fixed] genkey: Generate a 256 bits key tests: launch some tests between a client and a weak server oracle-weak: launch a padding oracle attack against a weak server @@ -26,34 +26,88 @@ fn print_usage() { ); } -fn main() { +fn do_oracle_attack(address: &str, variant: packet::Variant) { + // 16 bytes encrypted data from 'Packet::random_packet_data([4])'. + let cypher_block: [u8, ..16] = [254, 9, 228, 149, 60, 42, 165, 34, 233, 75, 112, 57, 37, 9, 116, 103]; // Known by the attacker. + let xor_operand: [u8, ..16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]; // This is the IV or the previous 16 bytes cypherblock. In our case we took the IV. + + let expected_clear_block: [u8, ..16] = [44, 92, 31, 98, 220, 84, 226, 53, 58, 94, 45, 25, 242, 6, 199, 1]; // To be found by the attacker. + + match oracle_machine::decypher(address, PORT, &xor_operand, &cypher_block, variant) { + Some(ref decyphered) if decyphered.as_slice() == expected_clear_block => { + println!("The oracle machine has found the clear block!:"); + println!(" Expected block: {}", expected_clear_block.to_vec()); + println!(" Decrypted block: {}", decyphered) + } + Some(ref other) => + println!("The oracle machine hasn't found the clear block: {}", other), + _ => + println!("The oracle machine hasn't found the clear block"), + } +} + +enum Mode { + Help, + ServerAlone, + GenKey, + Tests, + OracleWeak, + OracleFixed, +} + +fn mode() -> Mode { let args = os::args(); if args.iter().any(|a| a.as_slice() == "--help" || a.as_slice() == "-h") { - print_usage(); - } else if args.len() > 1 && args[1].as_slice() == "genkey" { - match crypto::generate_key(256 / 8) { - Ok(key) => println!("key: {}", key), - Err(e) => println!("Unable to generate a key. Error: {}", e) - } + return Help + } + + if args.len() <= 1 { + ServerAlone } else { - println!("Starting server..."); + match args[1].as_slice() { + "genkey" => GenKey, + "tests" => Tests, + "oracle-weak" => OracleWeak, + "oracle-fixed" => OracleFixed, + _ => ServerAlone, + } + } +} - match Server::new("::1", PORT) { - Ok(mut server) => { - println!("Server started"); +fn main() { + let mode = mode(); + + match mode { + Help => print_usage(), + GenKey => + match crypto::generate_key(256 / 8) { + Ok(key) => println!("key: {}", key), + Err(e) => println!("Unable to generate a key. Error: {}", e) + }, + _ => { + let address = "::1"; + println!("Starting server on [{}]:{}...", address, PORT); - if args.len() > 1 && args[1].as_slice() == "tests" { - Client::start_tests("::1", PORT); - } + match Server::new(address, PORT, match mode { OracleFixed => packet::Fixed, _ => packet::Weak }) { + Ok(mut server) => { + println!("Server started"); - println!("Press any key to quit"); - io::stdin().read_line().ok().expect("Failed to read line"); + match mode { + Tests => Client::start_tests(address, PORT, packet::Weak), + OracleWeak => do_oracle_attack(address, packet::Weak), + OracleFixed => do_oracle_attack(address, packet::Fixed), + _ => { + println!("Press any key to quit"); + io::stdin().read_line().ok().expect("Failed to read line"); + } + } - server.close().ok().expect("Failed to close the server"); - }, - Err(e) => - println!("Unable to create a new server. Error: {}", e) + server.close().ok().expect("Failed to close the server"); + }, + Err(e) => + println!("Unable to create a new server. Error: {}", e) + } } } } diff --git a/lab1_rust/src/oracle_machine.rs b/lab1_rust/src/oracle_machine.rs index 68aef94..2feb31b 100644 --- a/lab1_rust/src/oracle_machine.rs +++ b/lab1_rust/src/oracle_machine.rs @@ -1,19 +1,97 @@ use std::io; use std::io::{ TcpStream }; +use std::iter::{ range_inclusive }; +use std::slice::bytes::copy_memory; +use packet; +use packet::{ Packet, Error }; use end_point::EndPoint; -/// Try to decypher a cyphered data block by using an oracle on the provided address and port. +/// Try to decypher a cyphered data block by using the previous xor operand and an oracle on the provided address and port. /// May prints some message on the stdout. -pub fn decypher(address: &str, port: u16, cypherblock: [u8, ..16]) -> Option> { - let end_point = EndPoint::new( +pub fn decypher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cypherblock: &[u8, ..16], variant: packet::Variant) -> Option> { + let mut end_point = EndPoint::new( match TcpStream::connect(address, port) { Ok(s) => s, _ => { - println!("Unable to connect to the oracle on {}:{}", address, port); + println!("Unable to connect to the oracle on [{}]:{}", address, port); return None } - } + }, + variant, ); - None -} + let mut final_packet = [0u8, ..2 + 1 + 8 + 32 + 10]; + final_packet[1] = 1 + 8 + 32 + 10; // Data length. + copy_memory(final_packet.slice_mut(2 + 1 + 8 + 16, 2 + 1 + 8 + 32), cypherblock); + + let mut decypher_block = [0u8, ..16]; // The result. + let mut x_prime_block = [0u8, ..16]; // The cypher block ('cypherblock') after AES and before XOR. + let mut current_timestamp = 0u64; + let mut first_byte = 0u8; // Used to save the first byte for the first iteration. + + #[inline(always)] + fn forged_xor_operand(packet: &mut [u8]) -> &mut [u8] { packet.slice_mut(2 + 1 + 8, 2 + 1 + 8 + 16) } + + // For each bytes. + let mut byte = 15; + 'main_loop: loop { + let mut get_mac_mismatch_error = false; // For the first byte we need to insure there is only one AUTH error (one valid padding). + + for v in range_inclusive(0u8, 255) { // For each values of the current byte. + + // Compute and write timestamp. + current_timestamp += 2; + { + let mut timestamp_writer = io::BufWriter::new(final_packet.slice_mut(2 + 1, 2 + 1 + 8)); + let _ = timestamp_writer.write_be_u64(current_timestamp - 1); + } + + forged_xor_operand(&mut final_packet)[byte] = v; + + match end_point.send_raw_with_result(final_packet) { + Ok(Ok(Packet { t: Error(packet::AuthError), .. })) => { + + // If we already got a MAC mismatch for the first byte then the second byte is incremented and the loop is replayed. + if byte == 15 && get_mac_mismatch_error { + forged_xor_operand(&mut final_packet)[14] += 1; + continue 'main_loop; + } + + get_mac_mismatch_error = true; + + let padding_value = 16 - byte; + x_prime_block[byte] = v ^ (padding_value as u8); + decypher_block[byte] = x_prime_block[byte] ^ original_xor_operand[byte]; + + // We set the processed bytes of the forged XOR operand to have the next padding value. + for i in range(16 - padding_value, 16) { + forged_xor_operand(&mut final_packet)[i] = x_prime_block[i] ^ ((padding_value as u8) + 1); + } + + // Special case for the first byte: we have to test all the values. + if byte == 15 { + first_byte = forged_xor_operand(&mut final_packet)[15]; + } else { + break; + } + }, + Ok(Ok(Packet { t: Error(packet::CryptError), .. })) => (), // Ignored case: the padding is wrong. + other => { + println!("Unexcepted response, aborting. {}", other); + return None + } + } + } + + if byte == 15 { + forged_xor_operand(&mut final_packet)[15] = first_byte; + } + + // It was the last byte. + if byte == 0 { break; } + + byte -= 1; + } + + Some(decypher_block.to_vec()) +} \ No newline at end of file diff --git a/lab1_rust/src/packet.rs b/lab1_rust/src/packet.rs index 73eecea..95468a1 100644 --- a/lab1_rust/src/packet.rs +++ b/lab1_rust/src/packet.rs @@ -5,6 +5,11 @@ use std::rand::distributions::IndependentSample; use serialize::hex::{ ToHex }; use crypto; +pub enum Variant { + Weak, // The MAC is computed on data without padding. + Fixed // The MAC is computed on data and padding. +} + // There are all the errors that may occur when reading an encrypted and authenticated packet. #[deriving(Show)] pub enum ReadingError { @@ -78,7 +83,9 @@ pub enum PacketType { /// P: Padding from 1 to 16, |I|C...C|P...P| size must be a multiple of 16 /// |0000000000000000| for error packet (16 bytes length) /// MMMMMMMMMM: first 10 bytes (most significant) of the HMAC-SHA256 of: -/// |I|C...C| for command and answer packet +/// for command and answer packet: +/// |I|C...C| for weak variant +/// |I|C...C|P...P|for fixed variant /// |0000000000000000| for error packet #[deriving(Show)] pub struct Packet { @@ -114,15 +121,15 @@ impl Packet { PacketData { id: id, payload: payload } } - pub fn write(&self, output: &mut io::Writer) -> WritingResult { - self.write_with_padding_fun(output, |_, padding_length: uint| -> u8 { + pub fn write(&self, output: &mut io::Writer, variant: Variant) -> WritingResult { + self.write_with_padding_fun(output, variant, |_, padding_length: uint| -> u8 { padding_length as u8 }) } /// 'padd_fun' is function defining the padding. The first argument is the index of the current byte, starting at 0. /// The second argument is the padding length. - pub fn write_with_padding_fun(&self, output: &mut io::Writer, padd_fun: |uint, uint| -> u8) -> WritingResult { + pub fn write_with_padding_fun(&self, output: &mut io::Writer, variant: Variant, padd_fun: |uint, uint| -> u8) -> WritingResult { fn packet_data(p: &PacketData) -> Vec { let mut d = Vec::new(); d.push(p.id); @@ -137,8 +144,7 @@ impl Packet { Error(_) => Vec::from_elem(16, 0) // Padding as data: 16 * 0. }; - // Compute the MAC - let mac = crypto::compute_mac(data.as_slice()); + let data_size = data.len(); // Padding. match self.t { @@ -152,6 +158,9 @@ impl Packet { _ => () } + // Compute the MAC. It depends the choosen variant. + let mac = crypto::compute_mac(data.slice_to(match variant { Weak => data_size, _ => data.len() })); + // Encrypt. let encrypted_data = match crypto::encrypt(data.as_slice(), iv_from_timestamp(self.timestamp).as_slice()) { Some(d) => d, @@ -183,7 +192,7 @@ impl Packet { Ok(()) } - pub fn read(input: &mut io::Reader) -> ReadingResult { + pub fn read(input: &mut io::Reader, variant: Variant) -> ReadingResult { fn consume(input: &mut io::Reader, nb_byte: uint) { let _ = input.read_exact(nb_byte); } @@ -208,12 +217,19 @@ impl Packet { _ => return Err(UnconsistentEncryptedSizeError) }; + // Read the MAC. + let mut mac_read = [0u8, ..10]; + if try_read_io!(input.read(mac_read)) != mac_read.len() { + return Err(UnconsistentMACSizeError) + } + + match variant { Fixed if mac_read != crypto::compute_mac(data.as_slice()) => return Err(MACMismatchError), _ => () }; + // Control the size and the content of the padding then remove it. if packet_type == 0x00 || packet_type == 0xFF { match data.last() { Some(&padding_size) => { - if padding_size as uint > data.len() || !data.slice_from(data.len() - padding_size as uint).iter().any(|b| *b == padding_size) { - consume(input, 10); + if padding_size as uint > data.len() || padding_size == 0 || data.slice_from(data.len() - padding_size as uint).iter().any(|b| *b != padding_size) { return Err(PaddingError) } let data_length = data.len() - padding_size as uint; @@ -224,15 +240,7 @@ impl Packet { } } - // Read an verify the MAC. - let mut mac_read = [0u8, ..10]; - if try_read_io!(input.read(mac_read)) != mac_read.len() { - return Err(UnconsistentMACSizeError) - } - let mac_data = crypto::compute_mac(data.as_slice()); - if mac_read != mac_data { - return Err(MACMismatchError) - } + match variant { Weak if mac_read != crypto::compute_mac(data.as_slice()) => return Err(MACMismatchError), _ => () }; Ok(Packet { t: match packet_type {