* Oracle machine finished.
authorUmmon <greg.burri@gmail.com>
Wed, 5 Nov 2014 16:27:52 +0000 (17:27 +0100)
committerUmmon <greg.burri@gmail.com>
Wed, 5 Nov 2014 16:27:52 +0000 (17:27 +0100)
* Add a fixed version of the protocol.

lab1_rust/run_attack.sh [new file with mode: 0755]
lab1_rust/src/end_point.rs
lab1_rust/src/main.rs
lab1_rust/src/oracle_machine.rs
lab1_rust/src/packet.rs

diff --git a/lab1_rust/run_attack.sh b/lab1_rust/run_attack.sh
new file mode 100755 (executable)
index 0000000..2850e68
--- /dev/null
@@ -0,0 +1 @@
+time cargo run --release -- oracle-weak
index 2d0a66c..cb67226 100644 (file)
@@ -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<Server> {
+   pub fn new(interface: &str, port: u16, variant: packet::Variant) -> IoResult<Server> {
       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<Client> {
+   pub fn new(address: &str, port: u16, variant: packet::Variant) -> IoResult<Client> {
       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<u8> {
          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<ReadingResult> {
       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<ReadingResult> {
+   pub fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult<ReadingResult> {
       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);
index 64aef28..0b14951 100644 (file)
@@ -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)
+         }
       }
    }
 }
index 68aef94..2feb31b 100644 (file)
@@ -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<Vec<u8>> {
-   let end_point = EndPoint::new(
+pub fn decypher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cypherblock: &[u8, ..16], variant: packet::Variant) -> Option<Vec<u8>> {
+   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
index 73eecea..95468a1 100644 (file)
@@ -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<u8> {
          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 {