From d28b2b63d35c420093ee109ca5a35261636041bb Mon Sep 17 00:00:00 2001 From: SkeletalDemise <29117662+SkeletalDemise@users.noreply.github.com> Date: Mon, 12 Dec 2022 00:36:36 -0800 Subject: [PATCH] Add Hexadecimal (#140) * Add Hexadecimal Adds a hexadecimal decoder. * Clean up hexadecimal encoder * Added one last test * Add error propagation and 0x support * Adds error propagation * Adds support for 0x delimiters * Merges redundant tests * Adds 2 more tests Co-authored-by: autumn skerritt --- src/decoders/hexadecimal_decoder.rs | 234 ++++++++++++++++++++++++++++ src/decoders/mod.rs | 2 + src/filtration_system/mod.rs | 3 + 3 files changed, 239 insertions(+) create mode 100644 src/decoders/hexadecimal_decoder.rs diff --git a/src/decoders/hexadecimal_decoder.rs b/src/decoders/hexadecimal_decoder.rs new file mode 100644 index 00000000..cf80697f --- /dev/null +++ b/src/decoders/hexadecimal_decoder.rs @@ -0,0 +1,234 @@ +use crate::checkers::CheckerTypes; +use crate::decoders::interface::check_string_success; + +use super::crack_results::CrackResult; +use super::interface::Crack; +use super::interface::Decoder; + +use log::{debug, info, trace}; + +///! Hexadecimal Decoder +pub struct HexadecimalDecoder; + +///! Error enum +#[derive(Debug)] +enum Error { + ///! Error when the input is not divisible by 2 + InvalidLength, + ///! Error if the result isn't UTF-8 + InvalidUtf8, +} + +impl Crack for Decoder { + fn new() -> Decoder { + Decoder { + name: "hexadecimal", + description: "Data is broken into 4-bit sequences, and each value (between 0 and 15 inclusively) is encoded using one of 16 symbols from the ASCII character set. Although any 16 symbols from the ASCII character set can be used, in practice the ASCII digits '0'–'9' and the letters 'A'–'F' (or the lowercase 'a'–'f') are always chosen in order to align with standard written notation for hexadecimal numbers.", + link: "https://en.wikipedia.org/wiki/Hexadecimal#Base16_(transfer_encoding)", + tags: vec!["hexadecimal", "hex", "base", "decoder"], + expected_runtime: 0.01, + expected_success: 1.0, + failure_runtime: 0.01, + normalised_entropy: vec![1.0, 10.0], + popularity: 1.0, + phantom: std::marker::PhantomData, + } + } + + /// This function does the actual decoding + /// It returns an Option if it was successful + /// Else the Option returns nothing and the error is logged in Trace + fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult { + trace!("Trying hexadecimal with text {:?}", text); + let decoded_text: Result = hexadecimal_to_string(text); + let mut results = CrackResult::new(self, text.to_string()); + + if decoded_text.is_err() { + debug!("Failed to decode hexadecimal: {:?}", decoded_text); + return results; + } + + trace!("Decoded text for hexadecimal: {:?}", decoded_text); + + let decoded_text = decoded_text.unwrap(); + + if !check_string_success(&decoded_text, text) { + info!( + "Failed to decode hexadecimal because check_string_success returned false on string {}", + decoded_text + ); + return results; + } + + let checker_result = checker.check(&decoded_text); + results.unencrypted_text = Some(vec![decoded_text]); + + results.update_checker(&checker_result); + + results + } +} + +/// Decodes hexadecimal to string +fn hexadecimal_to_string(hex: &str) -> Result { + // Remove "0x" delimiters + let hex = hex.replace("0x", ""); + // Remove all non-hexadecimal characters from the string + let hex = hex.replace(|c: char| !c.is_ascii_hexdigit(), ""); + + // Convert the hexadecimal string to a vector of bytes + let bytes = hex.as_bytes(); + + // Ensure that the vector of bytes has an even length, so it can be processed in pairs + if bytes.len() % 2 == 1 { + return Err(Error::InvalidLength); + } + + // Iterate over the vector of bytes in pairs + let mut result = String::new(); + for pair in bytes.chunks(2) { + // Parse the pair of bytes as a hexadecimal number and push the corresponding + // ASCII character to the result string + result.push(u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap() as char); + } + + String::from_utf8(result.into()).map_err(|_| Error::InvalidUtf8) +} + +#[cfg(test)] +mod tests { + use super::HexadecimalDecoder; + use crate::{ + checkers::{ + athena::Athena, + checker_type::{Check, Checker}, + CheckerTypes, + }, + decoders::interface::{Crack, Decoder}, + }; + + // helper for tests + fn get_athena_checker() -> CheckerTypes { + let athena_checker = Checker::::new(); + CheckerTypes::CheckAthena(athena_checker) + } + + #[test] + fn hexadecimal_with_no_spaces_decodes_successfully() { + // This tests if Hexadecimal can decode Hexadecimal with no spaces successfully + let decoder = Decoder::::new(); + let result = decoder.crack( + "537068696e78206f6620626c61636b2071756172747a2c206a75646765206d7920766f772e", + &get_athena_checker(), + ); + assert_eq!( + result.unencrypted_text.unwrap()[0], + "Sphinx of black quartz, judge my vow." + ); + } + + #[test] + fn hexadecimal_with_spaces_decodes_successfully() { + // This tests if Hexadecimal can decode Hexadecimal with spaces successfully + // We use the hex string from the "c4ptur3-th3-fl4g" THM room + let decoder = Decoder::::new(); + let result = decoder.crack( + "68 65 78 61 64 65 63 69 6d 61 6c 20 6f 72 20 62 61 73 65 31 36 3f", + &get_athena_checker(), + ); + assert_eq!( + result.unencrypted_text.unwrap()[0], + "hexadecimal or base16?" + ); + } + + #[test] + fn hexadecimal_with_delimiters_decodes_successfully() { + // This tests if Hexadecimal can decode Hexadecimal with delimiters successfully + let decoder = Decoder::::new(); + let result = decoder.crack( + "68;74;74;70;73;3a;2f;2f;77;77;77;2e;67;6f;6f;67;6c;65;2e;63;6f;6d", + &get_athena_checker(), + ); + assert_eq!( + result.unencrypted_text.unwrap()[0], + "https://www.google.com" + ); + } + + #[test] + fn uppercase_hexadecimal_decodes_successfully() { + // This tests if Hexadecimal can decode uppercase Hexadecimal successfully + let decoder = Decoder::::new(); + let result = decoder.crack( + "5570706572636173652068657861646563696D616C", + &get_athena_checker(), + ); + assert_eq!(result.unencrypted_text.unwrap()[0], "Uppercase hexadecimal"); + } + + #[test] + fn hexadecimal_with_0x_delimiters_decodes_successfully() { + // This tests if Hexadecimal can decode Hexadecimal with 0x delimiters successfully + let decoder = Decoder::::new(); + let result = decoder.crack( + "0x540x680x690x730x200x750x730x650x730x200x300x780x200x610x730x200x740x680x650x200x700x720x650x660x690x780x200x620x650x740x770x650x650x6e0x200x650x760x650x720x790x200x630x680x750x6e0x6b", + &get_athena_checker(), + ); + assert_eq!( + result.unencrypted_text.unwrap()[0], + "This uses 0x as the prefix between every chunk" + ); + } + + #[test] + fn hexadecimal_with_0x_and_comma_delimiters_decodes_successfully() { + // This tests if Hexadecimal can decode Hexadecimal with 0x and comma delimiters successfully + let decoder = Decoder::::new(); + let result = decoder.crack( + "0x48,0x65,0x78,0x61,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x20,0x77,0x69,0x74,0x68,0x20,0x30,0x78,0x20,0x2b,0x20,0x63,0x6f,0x6d,0x6d,0x61,0x73", + &get_athena_checker(), + ); + assert_eq!( + result.unencrypted_text.unwrap()[0], + "Hexadecimal with 0x + commas" + ); + } + + #[test] + fn hexadecimal_handles_panics() { + // This tests if Hexadecimal can handle panics + // It should return Some + // This is because Hexadecimal can technically decode it, but it will be gibberish + let hexadecimal_decoder = Decoder::::new(); + let result = hexadecimal_decoder + .crack( + "hello my name is panicky mc panic face!", + &get_athena_checker(), + ) + .unencrypted_text; + assert!(result.is_some()); + } + + #[test] + fn hexadecimal_handles_panic_if_empty_string() { + // This tests if Hexadecimal can handle an empty string + // It should return None + let citrix_ctx1_decoder = Decoder::::new(); + let result = citrix_ctx1_decoder + .crack("", &get_athena_checker()) + .unencrypted_text; + assert!(result.is_none()); + } + + #[test] + fn hexadecimal_handles_panic_if_emoji() { + // This tests if Hexadecimal can handle an emoji + // It should return None + let base64_url_decoder = Decoder::::new(); + let result = base64_url_decoder + .crack("😂", &get_athena_checker()) + .unencrypted_text; + assert!(result.is_none()); + } +} diff --git a/src/decoders/mod.rs b/src/decoders/mod.rs index 4924d6f4..42af2513 100644 --- a/src/decoders/mod.rs +++ b/src/decoders/mod.rs @@ -15,6 +15,8 @@ pub mod base58_bitcoin_decoder; pub mod base58_monero_decoder; /// The binary_decoder module decodes binary pub mod binary_decoder; +/// The hexadecimal_decoder module decodes hexadecimal +pub mod hexadecimal_decoder; /// The base58_ripple_decoder module decodes base58 ripple pub mod base58_ripple_decoder; diff --git a/src/filtration_system/mod.rs b/src/filtration_system/mod.rs index 45cf752e..1e652149 100644 --- a/src/filtration_system/mod.rs +++ b/src/filtration_system/mod.rs @@ -6,6 +6,7 @@ use crate::decoders::base32_decoder::Base32Decoder; use crate::decoders::base58_bitcoin_decoder::Base58BitcoinDecoder; use crate::decoders::base58_monero_decoder::Base58MoneroDecoder; use crate::decoders::binary_decoder::BinaryDecoder; +use crate::decoders::hexadecimal_decoder::HexadecimalDecoder; use crate::decoders::base58_flickr_decoder::Base58FlickrDecoder; use crate::decoders::base58_ripple_decoder::Base58RippleDecoder; @@ -102,6 +103,7 @@ impl MyResults { pub fn filter_and_get_decoders() -> Decoders { trace!("Filtering and getting all decoders"); let binary = Decoder::::new(); + let hexadecimal = Decoder::::new(); let base58_bitcoin = Decoder::::new(); let base58_monero = Decoder::::new(); let base58_ripple = Decoder::::new(); @@ -118,6 +120,7 @@ pub fn filter_and_get_decoders() -> Decoders { Decoders { components: vec![ Box::new(binary), + Box::new(hexadecimal), Box::new(base58_bitcoin), Box::new(base58_monero), Box::new(base58_ripple),