Skip to main content

autoit/
format.rs

1//! Format metadata and analysis observations.
2
3use crate::{Encoding, InputKind};
4
5/// Summary of recognized container-level facts.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ContainerInfo {
8    input_kind: InputKind,
9    encoding: Option<Encoding>,
10    autoit_signature_offset: Option<usize>,
11    version_marker: Option<VersionMarkerInfo>,
12    payload_stream_offsets: Vec<usize>,
13    pe_script_resource: Option<PeScriptResourceInfo>,
14    packed_markers: Vec<PackedMarkerInfo>,
15}
16
17impl ContainerInfo {
18    /// Builds container facts from recorded observations.
19    ///
20    /// Folds the recorded observations into a single summary, capturing the
21    /// signature offset, version marker, payload stream offsets, PE script
22    /// resource, and packed markers. Observations without container facts
23    /// ([`Observation::OuterPeHeaderFound`],
24    /// [`Observation::FirstFileRecordDecrypted`], and
25    /// [`Observation::KnownSubtypeDecrypted`]) are ignored.
26    ///
27    /// # Arguments
28    ///
29    /// * `input_kind` - The recognized outer [`InputKind`].
30    /// * `encoding` - The recognized [`Encoding`], if any.
31    /// * `observations` - Observations to fold into the summary.
32    ///
33    /// # Returns
34    ///
35    /// A populated [`ContainerInfo`].
36    #[must_use]
37    pub fn from_observations(
38        input_kind: InputKind,
39        encoding: Option<Encoding>,
40        observations: &[Observation],
41    ) -> Self {
42        let mut info = Self {
43            input_kind,
44            encoding,
45            autoit_signature_offset: None,
46            version_marker: None,
47            payload_stream_offsets: Vec::new(),
48            pe_script_resource: None,
49            packed_markers: Vec::new(),
50        };
51        for observation in observations {
52            match *observation {
53                Observation::AutoItSignatureFound { offset } => {
54                    info.autoit_signature_offset = Some(offset);
55                }
56                Observation::VersionMarkerFound { encoding, offset } => {
57                    info.version_marker = Some(VersionMarkerInfo { encoding, offset });
58                }
59                Observation::PayloadStreamStartFound { offset } => {
60                    info.payload_stream_offsets.push(offset);
61                }
62                Observation::PeScriptResourceFound {
63                    type_id,
64                    type_name,
65                    name,
66                    language_id,
67                    rva,
68                    offset,
69                    size,
70                } => {
71                    info.pe_script_resource = Some(PeScriptResourceInfo {
72                        type_id,
73                        type_name,
74                        name,
75                        language_id,
76                        rva,
77                        offset,
78                        size,
79                    });
80                }
81                Observation::PackedMarkerFound { name, offset } => {
82                    info.packed_markers.push(PackedMarkerInfo { name, offset });
83                }
84                Observation::OuterPeHeaderFound
85                | Observation::FirstFileRecordDecrypted { .. }
86                | Observation::KnownSubtypeDecrypted { .. } => {}
87            }
88        }
89        info
90    }
91
92    /// Returns recognized input kind.
93    ///
94    /// # Returns
95    ///
96    /// The [`InputKind`] recorded for this container.
97    #[must_use]
98    pub const fn input_kind(&self) -> InputKind {
99        self.input_kind
100    }
101
102    /// Returns recognized encoding.
103    ///
104    /// # Returns
105    ///
106    /// `Some` with the recognized [`Encoding`], or `None` when none was determined.
107    #[must_use]
108    pub const fn encoding(&self) -> Option<Encoding> {
109        self.encoding
110    }
111
112    /// Returns AutoIt signature offset when present.
113    ///
114    /// # Returns
115    ///
116    /// `Some` with the file offset of the `AU3!` signature, or `None` when no
117    /// signature was located.
118    #[must_use]
119    pub const fn autoit_signature_offset(&self) -> Option<usize> {
120        self.autoit_signature_offset
121    }
122
123    /// Returns version marker facts when present.
124    ///
125    /// # Returns
126    ///
127    /// `Some` with the [`VersionMarkerInfo`], or `None` when no version marker was
128    /// found.
129    #[must_use]
130    pub const fn version_marker(&self) -> Option<VersionMarkerInfo> {
131        self.version_marker
132    }
133
134    /// Returns payload stream start offsets.
135    ///
136    /// # Returns
137    ///
138    /// A slice of file offsets where record streams are expected to begin, in
139    /// recording order.
140    #[must_use]
141    pub fn payload_stream_offsets(&self) -> &[usize] {
142        self.payload_stream_offsets.as_slice()
143    }
144
145    /// Returns PE script resource facts when present.
146    ///
147    /// # Returns
148    ///
149    /// `Some` with the [`PeScriptResourceInfo`], or `None` when no PE script
150    /// resource was found.
151    #[must_use]
152    pub const fn pe_script_resource(&self) -> Option<PeScriptResourceInfo> {
153        self.pe_script_resource
154    }
155
156    /// Returns packed-container markers found in the outer input.
157    ///
158    /// # Returns
159    ///
160    /// A slice of [`PackedMarkerInfo`] entries, in recording order.
161    #[must_use]
162    pub fn packed_markers(&self) -> &[PackedMarkerInfo] {
163        self.packed_markers.as_slice()
164    }
165}
166
167/// Version marker facts.
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct VersionMarkerInfo {
170    /// Encoding represented by the marker.
171    pub encoding: Encoding,
172    /// File offset of the marker.
173    pub offset: usize,
174}
175
176/// PE `RT_RCDATA/SCRIPT` resource facts.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub struct PeScriptResourceInfo {
179    /// PE resource type id.
180    pub type_id: u16,
181    /// PE resource type name, when recognized.
182    pub type_name: Option<&'static str>,
183    /// PE resource name.
184    pub name: &'static str,
185    /// PE resource language id, when present.
186    pub language_id: Option<u16>,
187    /// Resource data RVA.
188    pub rva: u32,
189    /// Resource data file offset.
190    pub offset: usize,
191    /// Resource data size.
192    pub size: u32,
193}
194
195/// Packed-container marker facts.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct PackedMarkerInfo {
198    /// Marker name.
199    pub name: &'static str,
200    /// File offset where the marker begins.
201    pub offset: usize,
202}
203
204/// Log of analysis observations.
205///
206/// Records concrete observations made while analyzing an AutoIt payload. It
207/// intentionally contains facts rather than behavior labels.
208#[derive(Debug, Clone, Default, PartialEq, Eq)]
209pub struct ObservationLog {
210    entries: Vec<Observation>,
211}
212
213impl ObservationLog {
214    /// Creates an empty log.
215    ///
216    /// # Returns
217    ///
218    /// An [`ObservationLog`] holding no observations.
219    #[must_use]
220    pub const fn new() -> Self {
221        Self {
222            entries: Vec::new(),
223        }
224    }
225
226    /// Adds an observation.
227    ///
228    /// # Arguments
229    ///
230    /// * `observation` - The [`Observation`] to append, preserving recording order.
231    pub fn push(&mut self, observation: Observation) {
232        self.entries.push(observation);
233    }
234
235    /// Returns the recorded observations.
236    ///
237    /// # Returns
238    ///
239    /// A slice of the recorded [`Observation`] values in recording order.
240    #[must_use]
241    pub fn entries(&self) -> &[Observation] {
242        self.entries.as_slice()
243    }
244}
245
246/// A concrete observation made while analyzing an AutoIt payload.
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum Observation {
249    /// The outer input begins with an `MZ` PE header.
250    OuterPeHeaderFound,
251    /// The AutoIt `AU3!` signature prefix was found at the given file offset.
252    AutoItSignatureFound {
253        /// File offset where the AutoIt signature prefix begins.
254        offset: usize,
255    },
256    /// A supported AutoIt version marker was found at the given file offset.
257    VersionMarkerFound {
258        /// Encoding represented by the version marker.
259        encoding: Encoding,
260        /// File offset where the version marker begins.
261        offset: usize,
262    },
263    /// The offset where the AU3 record stream is expected to begin.
264    PayloadStreamStartFound {
265        /// File offset where record parsing should begin.
266        offset: usize,
267    },
268    /// A PE `RT_RCDATA` resource named `SCRIPT` was found.
269    PeScriptResourceFound {
270        /// PE resource type id.
271        type_id: u16,
272        /// PE resource type name, when recognized.
273        type_name: Option<&'static str>,
274        /// PE resource name.
275        name: &'static str,
276        /// PE resource language id, when present.
277        language_id: Option<u16>,
278        /// Resource data RVA.
279        rva: u32,
280        /// Resource data file offset.
281        offset: usize,
282        /// Resource data size in bytes.
283        size: u32,
284    },
285    /// A known packer marker was found in the outer container.
286    PackedMarkerFound {
287        /// Marker name.
288        name: &'static str,
289        /// File offset where the marker begins.
290        offset: usize,
291    },
292    /// The first AU3 record type decrypted to `FILE`.
293    FirstFileRecordDecrypted {
294        /// Encoding profile used for decryption.
295        encoding: Encoding,
296        /// File offset of the encrypted `FILE` marker.
297        offset: usize,
298    },
299    /// The first AU3 record subtype decrypted to a known script subtype.
300    KnownSubtypeDecrypted {
301        /// Encoding profile used for decryption.
302        encoding: Encoding,
303        /// File offset where the source record begins.
304        offset: usize,
305        /// Known subtype that was recovered.
306        subtype: KnownSubtype,
307    },
308}
309
310/// Known AU3 record subtype categories.
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum KnownSubtype {
313    /// Tokenized script subtype `>>>AUTOIT SCRIPT<<<`.
314    TokenizedScript,
315    /// UTF-16 text script subtype `>AUTOIT UNICODE SCRIPT<`.
316    UnicodeScript,
317    /// Plain text script subtype `>AUTOIT SCRIPT<`.
318    PlainScript,
319}