Skip to content

Commit

Permalink
Add Hexadecimal (#140)
Browse files Browse the repository at this point in the history
* 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 <github@skerritt.blog>
  • Loading branch information
SkeletalDemise and bee-san authored Dec 12, 2022
1 parent e92f1b1 commit d28b2b6
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
234 changes: 234 additions & 0 deletions src/decoders/hexadecimal_decoder.rs
Original file line number Diff line number Diff line change
@@ -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<HexadecimalDecoder> {
fn new() -> Decoder<HexadecimalDecoder> {
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<string> 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<String, Error> = 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<String, Error> {
// 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::<Athena>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::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::<HexadecimalDecoder>::new();
let result = base64_url_decoder
.crack("😂", &get_athena_checker())
.unencrypted_text;
assert!(result.is_none());
}
}
2 changes: 2 additions & 0 deletions src/decoders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/filtration_system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -102,6 +103,7 @@ impl MyResults {
pub fn filter_and_get_decoders() -> Decoders {
trace!("Filtering and getting all decoders");
let binary = Decoder::<BinaryDecoder>::new();
let hexadecimal = Decoder::<HexadecimalDecoder>::new();
let base58_bitcoin = Decoder::<Base58BitcoinDecoder>::new();
let base58_monero = Decoder::<Base58MoneroDecoder>::new();
let base58_ripple = Decoder::<Base58RippleDecoder>::new();
Expand All @@ -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),
Expand Down

0 comments on commit d28b2b6

Please sign in to comment.