ares/decoders/
citrix_ctx1_decoder.rs1use 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
10pub struct CitrixCTX1Decoder;
12
13#[derive(Debug)]
15enum Error {
16 InvalidLength,
18 LhsOverflow,
20 RhsOverflow,
22 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 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 fn get_tags(&self) -> &Vec<&str> {
72 &self.tags
73 }
74 fn get_name(&self) -> &str {
76 self.name
77 }
78}
79
80fn 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 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 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 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 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 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 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 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 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}