1use std::{env, fs, process::ExitCode};
4
5use autoit::{Artifact, AutoItBinary, RecognitionFailure, Record, Script, StringFinding};
6
7fn main() -> ExitCode {
8 let mut args = env::args_os();
9 let _program = args.next();
10 let mut path = None;
11 for arg in args {
12 if path.is_none() {
13 path = Some(arg);
14 } else {
15 eprintln!("usage: dump <autoit-exe-or-a3x>");
16 return ExitCode::FAILURE;
17 }
18 }
19 let Some(path) = path else {
20 eprintln!("usage: dump <autoit-exe-or-a3x>");
21 return ExitCode::FAILURE;
22 };
23
24 let data = match fs::read(&path) {
25 Ok(data) => data,
26 Err(err) => {
27 eprintln!("failed to read input: {err}");
28 return ExitCode::FAILURE;
29 }
30 };
31
32 match AutoItBinary::try_parse(&data) {
33 Ok(binary) => {
34 print_text(&binary);
35 ExitCode::SUCCESS
36 }
37 Err(err) if err.recognition_failure() == Some(RecognitionFailure::NotRecognized) => {
38 eprintln!("not recognized as AutoIt");
39 ExitCode::FAILURE
40 }
41 Err(err) => {
42 eprintln!("failed to parse AutoIt payload: {err}");
43 ExitCode::FAILURE
44 }
45 }
46}
47
48fn print_text(binary: &AutoItBinary) {
49 print_overview(binary);
50 print_container(binary);
51 print_observations(binary);
52 print_records(binary.records());
53 print_record_diagnostics(binary);
54 print_scripts(binary.scripts());
55 print_artifacts(binary.artifacts());
56 print_strings(binary.strings());
57}
58
59fn print_overview(binary: &AutoItBinary) {
60 section("overview");
61 field("input kind", format!("{:?}", binary.input_kind()));
62 field("encoding", opt_debug(binary.encoding()));
63 field("records", binary.records().len().to_string());
64 field("scripts", binary.scripts().len().to_string());
65 field("artifacts", binary.artifacts().len().to_string());
66 field("strings", binary.strings().len().to_string());
67 field(
68 "record diagnostics",
69 binary.record_diagnostics().len().to_string(),
70 );
71}
72
73fn print_container(binary: &AutoItBinary) {
74 let container = binary.container();
75 section("container");
76 field("input kind", format!("{:?}", container.input_kind()));
77 field("encoding", opt_debug(container.encoding()));
78 field(
79 "autoit signature offset",
80 container
81 .autoit_signature_offset()
82 .map_or_else(|| "none".to_string(), |offset| format!("{offset}")),
83 );
84 field(
85 "version marker",
86 container.version_marker().map_or_else(
87 || "none".to_string(),
88 |marker| format!("{:?} @ offset {}", marker.encoding, marker.offset),
89 ),
90 );
91 field(
92 "payload stream offsets",
93 if container.payload_stream_offsets().is_empty() {
94 "none".to_string()
95 } else {
96 format!("{:?}", container.payload_stream_offsets())
97 },
98 );
99
100 match container.pe_script_resource() {
101 None => field("pe script resource", "none".to_string()),
102 Some(resource) => {
103 println!(" pe script resource:");
104 subfield(
105 "type",
106 match resource.type_name {
107 Some(name) => format!("{} ({name})", resource.type_id),
108 None => resource.type_id.to_string(),
109 },
110 );
111 subfield("name", escape_inline(resource.name));
112 subfield(
113 "language id",
114 resource
115 .language_id
116 .map_or_else(|| "none".to_string(), |id| id.to_string()),
117 );
118 subfield("rva", format!("{:#x}", resource.rva));
119 subfield("file offset", resource.offset.to_string());
120 subfield("size", format!("{} bytes", resource.size));
121 }
122 }
123
124 if container.packed_markers().is_empty() {
125 field("packed markers", "none".to_string());
126 } else {
127 println!(" packed markers:");
128 for marker in container.packed_markers() {
129 println!(" {} @ offset {}", marker.name, marker.offset);
130 }
131 }
132}
133
134fn print_observations(binary: &AutoItBinary) {
135 let observations = binary.observations().entries();
136 section(&format!("observations ({})", observations.len()));
137 for observation in observations {
138 println!(" {observation:?}");
139 }
140}
141
142fn print_records(records: &[Record]) {
143 section(&format!("records ({})", records.len()));
144 for record in records {
145 println!(" #{} @ offset {}", record.index(), record.offset());
146 subfield("subtype", escape_inline(record.subtype()));
147 subfield("name", escape_inline(record.name()));
148 let profile = record.profile();
149 subfield(
150 "profile",
151 format!(
152 "encoding={:?} encryption={:?} compression={:?}",
153 profile.encoding, profile.encryption, profile.compression
154 ),
155 );
156 subfield(
157 "compressed",
158 format!(
159 "{} (compressed_size={}, uncompressed_size={})",
160 record.compressed(),
161 record.compressed_size(),
162 record.uncompressed_size()
163 ),
164 );
165 subfield(
166 "checksum",
167 format!(
168 "{:#010x} ({})",
169 record.checksum(),
170 if record.checksum_valid() {
171 "valid"
172 } else {
173 "invalid"
174 }
175 ),
176 );
177 subfield(
178 "timestamps",
179 format!(
180 "created={} last_write={}",
181 record.creation_time(),
182 record.last_write_time()
183 ),
184 );
185 subfield(
186 "data sizes",
187 format!(
188 "encrypted={} decrypted={} decompressed={} payload={}",
189 record.encrypted_data().len(),
190 record.decrypted_data().len(),
191 record
192 .decompressed_data()
193 .map_or_else(|| "none".to_string(), |data| data.len().to_string()),
194 record.payload_data().len()
195 ),
196 );
197 subfield(
198 "decompression",
199 format!("{:?}", record.decompression_status()),
200 );
201 }
202}
203
204fn print_record_diagnostics(binary: &AutoItBinary) {
205 let diagnostics = binary.record_diagnostics();
206 if diagnostics.is_empty() {
207 return;
208 }
209 section(&format!("record diagnostics ({})", diagnostics.len()));
210 for diagnostic in diagnostics {
211 println!(
212 " record #{} @ offset {}: {:?}",
213 diagnostic.record_index, diagnostic.offset, diagnostic.reason
214 );
215 }
216}
217
218fn print_scripts(scripts: &[Script]) {
219 section(&format!("scripts ({})", scripts.len()));
220 for script in scripts {
221 println!(
222 " record #{} {}",
223 script.record_index(),
224 escape_inline(script.name())
225 );
226 subfield("kind", format!("{:?}", script.kind()));
227 subfield("bytes", script.bytes().len().to_string());
228 subfield(
229 "timestamps",
230 format!(
231 "created={} last_write={}",
232 script.creation_time(),
233 script.last_write_time()
234 ),
235 );
236 subfield(
237 "text encoding",
238 script.text().map_or_else(
239 || "none".to_string(),
240 |text| format!("{:?}", text.encoding()),
241 ),
242 );
243 subfield("decode error", opt_debug(script.decode_error()));
244 match script.token_stream() {
245 None => subfield("token stream", "none".to_string()),
246 Some(tokens) => subfield(
247 "token stream",
248 format!(
249 "line_count={} tokens={}",
250 tokens.line_count(),
251 tokens.tokens().len()
252 ),
253 ),
254 }
255 match script.source_text() {
256 None => subfield("source", "<unavailable>".to_string()),
257 Some(source) => {
258 println!(" source ({} chars):", source.chars().count());
259 print_block(source);
260 }
261 }
262 }
263}
264
265fn print_artifacts(artifacts: &[Artifact]) {
266 section(&format!("artifacts ({})", artifacts.len()));
267 for artifact in artifacts {
268 println!(
269 " record #{} {}",
270 artifact.record_index(),
271 escape_inline(artifact.name())
272 );
273 subfield("subtype", escape_inline(artifact.subtype()));
274 subfield("bytes", artifact.bytes().len().to_string());
275 subfield(
276 "timestamps",
277 format!(
278 "created={} last_write={}",
279 artifact.creation_time(),
280 artifact.last_write_time()
281 ),
282 );
283 subfield("checksum valid", artifact.checksum_valid().to_string());
284 subfield(
285 "decompression",
286 format!("{:?}", artifact.decompression_status()),
287 );
288 }
289}
290
291fn print_strings(strings: &[StringFinding]) {
292 section(&format!("strings ({})", strings.len()));
293 for found in strings {
294 println!(
295 " record #{} offset {} {:?}: {}",
296 found.record_index(),
297 found.offset(),
298 found.encoding(),
299 escape_inline(found.value())
300 );
301 }
302}
303
304fn section(title: &str) {
306 println!("== {title} ==");
307}
308
309fn field(label: &str, value: String) {
311 println!(" {label}: {value}");
312}
313
314fn subfield(label: &str, value: String) {
316 println!(" {label}: {value}");
317}
318
319fn print_block(text: &str) {
321 for line in text.lines() {
322 println!(" | {line}");
323 }
324}
325
326fn opt_debug<T>(value: Option<T>) -> String
328where
329 T: core::fmt::Debug,
330{
331 value.map_or_else(|| "none".to_string(), |value| format!("{value:?}"))
332}
333
334fn escape_inline(value: &str) -> String {
337 let mut out = String::new();
338 for ch in value.chars() {
339 match ch {
340 '\n' => out.push_str("\\n"),
341 '\r' => out.push_str("\\r"),
342 '\t' => out.push_str("\\t"),
343 ch if ch.is_control() => out.push_str(format!("\\u{{{:04x}}}", u32::from(ch)).as_str()),
344 ch => out.push(ch),
345 }
346 }
347 out
348}