ares/decoders/
citrix_ctx1_decoder.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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};

///! Citrix CTX1 Decoder
pub struct CitrixCTX1Decoder;

///! Error enum
#[derive(Debug)]
enum Error {
    ///! Error when the input is not divisible by 4
    InvalidLength,
    ///! Error with left-hand side subtraction
    LhsOverflow,
    ///! Error with right-hand side subtraction
    RhsOverflow,
    ///! Error if the result isn't UTF-8
    InvalidUtf8,
}

impl Crack for Decoder<CitrixCTX1Decoder> {
    fn new() -> Decoder<CitrixCTX1Decoder> {
        Decoder {
            name: "Citrix Ctx1",
            description: "Citrix CTX1 is a very old encoding that was used for encoding Citrix passwords.",
            link: "https://www.remkoweijnen.nl/blog/2012/05/13/encoding-and-decoding-citrix-passwords/",
            tags: vec!["citrix_ctx1", "citrix", "passwords", "decoder"],
            popularity: 0.1,
            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 citrix_ctx1 with text {:?}", text);
        let decoded_text: Result<String, Error> = decode_citrix_ctx1(text);

        let mut results = CrackResult::new(self, text.to_string());

        if decoded_text.is_err() {
            debug!("Failed to decode citrix_ctx1: {:?}", decoded_text);
            return results;
        }

        trace!("Decoded text for citrix_ctx1: {:?}", decoded_text);

        let decoded_text = decoded_text.unwrap();
        if !check_string_success(&decoded_text, text) {
            info!(
                "Failed to decode citrix_ctx1 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
    }
    /// Gets all tags for this decoder
    fn get_tags(&self) -> &Vec<&str> {
        &self.tags
    }
    /// Gets the name for the current decoder
    fn get_name(&self) -> &str {
        self.name
    }
}

/// Decodes Citrix CTX1
fn decode_citrix_ctx1(text: &str) -> Result<String, Error> {
    if text.len() % 4 != 0 {
        return Err(Error::InvalidLength);
    }

    let mut rev = text.as_bytes().to_vec();
    rev.reverse();
    let mut result = Vec::new();
    let mut temp;

    for i in (0..rev.len()).step_by(2) {
        if i + 2 >= rev.len() {
            temp = 0;
        } else {
            temp = ((rev[i + 2].checked_sub(0x41)).ok_or(Error::LhsOverflow)? & 0xF)
                ^ (((rev[i + 3].checked_sub(0x41)).ok_or(Error::RhsOverflow)? << 4) & 0xF0);
        }
        temp ^= (((rev[i].checked_sub(0x41)).ok_or(Error::LhsOverflow)? & 0xF)
            ^ (((rev[i + 1].checked_sub(0x41)).ok_or(Error::RhsOverflow)? << 4) & 0xF0))
            ^ 0xA5;
        result.push(temp);
    }

    result.retain(|&x| x != 0);
    result.reverse();

    String::from_utf8(result).map_err(|_| Error::InvalidUtf8)
}

#[cfg(test)]
mod tests {
    use super::CitrixCTX1Decoder;
    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 citrix_ctx1_decodes_successfully() {
        // This tests if Citrix CTX1 can decode Citrix CTX1 successfully
        let decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = decoder.crack(
            "MNGIKIANMEGBKIANMHGCOHECJADFPPFKINCIOBEEIFCA",
            &get_athena_checker(),
        );
        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world");
    }

    #[test]
    fn citrix_ctx1_decodes_lowercase_successfully() {
        // This tests if Citrix CTX1 can decode lowercase strings
        let decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = decoder.crack(
            "pbfejjdmpaffidcgkdagmkgpljbmjjdmpffajkdponeiiicnpkfpjjdmpifnilcoooelmoglincioeebjadfocehilcopdfgndhgjadfmegbjmdjknai",
            &get_athena_checker(),
        );
        assert_eq!(
            result.unencrypted_text.unwrap()[0],
            "This is lowercase Citrix CTX1"
        );
    }

    #[test]
    fn citrix_ctx1_handles_substraction_overflow() {
        // This tests if Citrix CTX1 can handle substraction overflows
        // It should return None and not panic
        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = citrix_ctx1_decoder
            .crack("NUWEN43XR44TLAYHSU4DVI2ISF======", &get_athena_checker())
            .unencrypted_text;
        assert!(result.is_none());
    }

    #[test]
    fn citrix_ctx1_handles_length_not_divisible_by_4() {
        // This tests if Citrix CTX1 can handle strings with length that are not divisible by 4
        // It should return None
        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = citrix_ctx1_decoder
            .crack("AAA", &get_athena_checker())
            .unencrypted_text;
        assert!(result.is_none());
    }

    #[test]
    fn citrix_ctx1_decode_handles_panics() {
        // This tests if Citrix CTX1 can handle panics
        // It should return None
        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = citrix_ctx1_decoder
            .crack(
                "hello my name is panicky mc panic face!",
                &get_athena_checker(),
            )
            .unencrypted_text;
        assert!(result.is_none());
    }

    #[test]
    fn citrix_ctx1_handle_panic_if_empty_string() {
        // This tests if Citrix CTX1 can handle an empty string
        // It should return None
        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = citrix_ctx1_decoder
            .crack("", &get_athena_checker())
            .unencrypted_text;
        assert!(result.is_none());
    }

    #[test]
    fn citrix_ctx1_decodes_emoji_successfully() {
        // This tests if Citrix CTX1 can decode an emoji
        let citrix_ctx1_decoder = Decoder::<CitrixCTX1Decoder>::new();
        let result = citrix_ctx1_decoder.crack("😂", &get_athena_checker());
        assert_eq!(result.unencrypted_text.unwrap()[0], "[*");
    }
}