ares/decoders/
citrix_ctx1_decoder.rs

1use crate::checkers::CheckerTypes;
2use crate::decoders::interface::check_string_success;
3
4use super::crack_results::CrackResult;
5use super::interface::Crack;
6use super::interface::Decoder;
7
8use log::{debug, info, trace};
9
10///! Citrix CTX1 Decoder
11pub struct CitrixCTX1Decoder;
12
13///! Error enum
14#[derive(Debug)]
15enum Error {
16    ///! Error when the input is not divisible by 4
17    InvalidLength,
18    ///! Error with left-hand side subtraction
19    LhsOverflow,
20    ///! Error with right-hand side subtraction
21    RhsOverflow,
22    ///! Error if the result isn't UTF-8
23    InvalidUtf8,
24}
25
26impl Crack for Decoder<CitrixCTX1Decoder> {
27    fn new() -> Decoder<CitrixCTX1Decoder> {
28        Decoder {
29            name: "Citrix Ctx1",
30            description: "Citrix CTX1 is a very old encoding that was used for encoding Citrix passwords.",
31            link: "https://www.remkoweijnen.nl/blog/2012/05/13/encoding-and-decoding-citrix-passwords/",
32            tags: vec!["citrix_ctx1", "citrix", "passwords", "decoder"],
33            popularity: 0.1,
34            phantom: std::marker::PhantomData,
35        }
36    }
37
38    /// This function does the actual decoding
39    /// It returns an Option<string> if it was successful
40    /// Else the Option returns nothing and the error is logged in Trace
41    fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
42        trace!("Trying citrix_ctx1 with text {:?}", text);
43        let decoded_text: Result<String, Error> = decode_citrix_ctx1(text);
44
45        let mut results = CrackResult::new(self, text.to_string());
46
47        if decoded_text.is_err() {
48            debug!("Failed to decode citrix_ctx1: {:?}", decoded_text);
49            return results;
50        }
51
52        trace!("Decoded text for citrix_ctx1: {:?}", decoded_text);
53
54        let decoded_text = decoded_text.unwrap();
55        if !check_string_success(&decoded_text, text) {
56            info!(
57                "Failed to decode citrix_ctx1 because check_string_success returned false on string {}",
58                decoded_text
59            );
60            return results;
61        }
62
63        let checker_result = checker.check(&decoded_text);
64        results.unencrypted_text = Some(vec![decoded_text]);
65
66        results.update_checker(&checker_result);
67
68        results
69    }
70    /// Gets all tags for this decoder
71    fn get_tags(&self) -> &Vec<&str> {
72        &self.tags
73    }
74    /// Gets the name for the current decoder
75    fn get_name(&self) -> &str {
76        self.name
77    }
78}
79
80/// Decodes Citrix CTX1
81fn decode_citrix_ctx1(text: &str) -> Result<String, Error> {
82    if text.len() % 4 != 0 {
83        return Err(Error::InvalidLength);
84    }
85
86    let mut rev = text.as_bytes().to_vec();
87    rev.reverse();
88    let mut result = Vec::new();
89    let mut temp;
90
91    for i in (0..rev.len()).step_by(2) {
92        if i + 2 >= rev.len() {
93            temp = 0;
94        } else {
95            temp = ((rev[i + 2].checked_sub(0x41)).ok_or(Error::LhsOverflow)? & 0xF)
96                ^ (((rev[i + 3].checked_sub(0x41)).ok_or(Error::RhsOverflow)? << 4) & 0xF0);
97        }
98        temp ^= (((rev[i].checked_sub(0x41)).ok_or(Error::LhsOverflow)? & 0xF)
99            ^ (((rev[i + 1].checked_sub(0x41)).ok_or(Error::RhsOverflow)? << 4) & 0xF0))
100            ^ 0xA5;
101        result.push(temp);
102    }
103
104    result.retain(|&x| x != 0);
105    result.reverse();
106
107    String::from_utf8(result).map_err(|_| Error::InvalidUtf8)
108}
109
110#[cfg(test)]
111mod tests {
112    use super::CitrixCTX1Decoder;
113    use crate::{
114        checkers::{
115            athena::Athena,
116            checker_type::{Check, Checker},
117            CheckerTypes,
118        },
119        decoders::interface::{Crack, Decoder},
120    };
121
122    // helper for tests
123    fn get_athena_checker() -> CheckerTypes {
124        let athena_checker = Checker::<Athena>::new();
125        CheckerTypes::CheckAthena(athena_checker)
126    }
127
128    #[test]
129    fn citrix_ctx1_decodes_successfully() {
130        // This tests if Citrix CTX1 can decode Citrix CTX1 successfully
131        let decoder = Decoder::<CitrixCTX1Decoder>::new();
132        let result = decoder.crack(
133            "MNGIKIANMEGBKIANMHGCOHECJADFPPFKINCIOBEEIFCA",
134            &get_athena_checker(),
135        );
136        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world");
137    }
138
139    #[test]
140    fn citrix_ctx1_decodes_lowercase_successfully() {
141        // This tests if Citrix CTX1 can decode lowercase strings
142        let decoder = Decoder::<CitrixCTX1Decoder>::new();
143        let result = decoder.crack(
144            "pbfejjdmpaffidcgkdagmkgpljbmjjdmpffajkdponeiiicnpkfpjjdmpifnilcoooelmoglincioeebjadfocehilcopdfgndhgjadfmegbjmdjknai",
145            &get_athena_checker(),
146        );
147        assert_eq!(
148            result.unencrypted_text.unwrap()[0],
149            "This is lowercase Citrix CTX1"
150        );
151    }
152
153    #[test]
154    fn citrix_ctx1_handles_substraction_overflow() {
155        // This tests if Citrix CTX1 can handle substraction overflows
156        // It should return None and not panic
157        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
158        let result = citrix_ctx1_decoder
159            .crack("NUWEN43XR44TLAYHSU4DVI2ISF======", &get_athena_checker())
160            .unencrypted_text;
161        assert!(result.is_none());
162    }
163
164    #[test]
165    fn citrix_ctx1_handles_length_not_divisible_by_4() {
166        // This tests if Citrix CTX1 can handle strings with length that are not divisible by 4
167        // It should return None
168        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
169        let result = citrix_ctx1_decoder
170            .crack("AAA", &get_athena_checker())
171            .unencrypted_text;
172        assert!(result.is_none());
173    }
174
175    #[test]
176    fn citrix_ctx1_decode_handles_panics() {
177        // This tests if Citrix CTX1 can handle panics
178        // It should return None
179        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
180        let result = citrix_ctx1_decoder
181            .crack(
182                "hello my name is panicky mc panic face!",
183                &get_athena_checker(),
184            )
185            .unencrypted_text;
186        assert!(result.is_none());
187    }
188
189    #[test]
190    fn citrix_ctx1_handle_panic_if_empty_string() {
191        // This tests if Citrix CTX1 can handle an empty string
192        // It should return None
193        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
194        let result = citrix_ctx1_decoder
195            .crack("", &get_athena_checker())
196            .unencrypted_text;
197        assert!(result.is_none());
198    }
199
200    #[test]
201    fn citrix_ctx1_decodes_emoji_successfully() {
202        // This tests if Citrix CTX1 can decode an emoji
203        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
204        let result = citrix_ctx1_decoder.crack("😂", &get_athena_checker());
205        assert_eq!(result.unencrypted_text.unwrap()[0], "[*");
206    }
207}