Skip to main content

floss_cli/
results.rs

1//! FLOSS `-j/--json` 输出的强类型结构体。
2//!
3//! 结构与字段命名对齐 `flare-floss` 的 `floss/results.py` 与 `floss/render/json.py`:
4//! - JSON 由 Python dataclass `asdict()` 生成;
5//! - `datetime` 会被编码为 `ISO8601 + "Z"` 字符串;
6//! - `decoding_function_scores` 的 key 在 JSON 中是字符串(因为 JSON object key 只能是字符串),这里支持十进制与 `0x` 十六进制地址。
7
8use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12/// 地址类字段(函数地址、程序计数器、解码例程地址等)。
13pub type Address = u64;
14
15/// FLOSS JSON 顶层结构。
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct ResultDocument {
18    pub analysis: Analysis,
19    pub metadata: Metadata,
20    pub strings: Strings,
21}
22
23/// 字符串编码。
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[non_exhaustive]
26pub enum StringEncoding {
27    #[serde(rename = "ASCII")]
28    Ascii,
29    #[serde(rename = "UTF-16LE")]
30    Utf16Le,
31    #[serde(rename = "UTF-8")]
32    Utf8,
33}
34
35/// 内存地址类型(栈/全局/堆)。
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
38#[non_exhaustive]
39pub enum AddressType {
40    Stack,
41    Global,
42    Heap,
43}
44
45/// 栈字符串(stack strings / tight strings)。
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[derive(Eq)]
48pub struct StackString {
49    pub encoding: StringEncoding,
50    pub frame_offset: i64,
51    pub function: Address,
52    pub offset: i64,
53    pub original_stack_pointer: Address,
54    pub program_counter: Address,
55    pub stack_pointer: Address,
56    pub string: String,
57}
58
59/// 解码字符串(decoded strings)。
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[derive(Eq)]
62pub struct DecodedString {
63    pub address: Address,
64    pub address_type: AddressType,
65    pub decoded_at: Address,
66    pub decoding_routine: Address,
67    pub encoding: StringEncoding,
68    pub string: String,
69}
70
71/// 静态字符串(static strings / language strings)。
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73#[derive(Eq)]
74pub struct StaticString {
75    pub encoding: StringEncoding,
76    pub offset: Address,
77    pub string: String,
78}
79
80/// 运行时统计(耗时,单位秒)。
81#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
82pub struct Runtime {
83    #[serde(default)]
84    pub decoded_strings: f64,
85    #[serde(default)]
86    pub find_features: f64,
87    #[serde(default)]
88    pub language_strings: f64,
89    #[serde(default)]
90    pub stack_strings: f64,
91    #[serde(default)]
92    pub start_date: String,
93    #[serde(default)]
94    pub static_strings: f64,
95    #[serde(default)]
96    pub tight_strings: f64,
97    #[serde(default)]
98    pub total: f64,
99    #[serde(default)]
100    pub vivisect: f64,
101}
102
103/// 被识别为“字符串解码函数”的评分信息。
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct DecodingFunctionScore {
106    pub score: f64,
107    #[serde(deserialize_with = "deserialize_u64_from_number")]
108    pub xrefs_to: u64,
109}
110
111/// 函数相关统计信息。
112#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
113pub struct Functions {
114    #[serde(default)]
115    pub analyzed_decoded_strings: u64,
116    #[serde(default)]
117    pub analyzed_stack_strings: u64,
118    #[serde(default)]
119    pub analyzed_tight_strings: u64,
120    #[serde(default, deserialize_with = "deserialize_address_key_map")]
121    pub decoding_function_scores: BTreeMap<Address, DecodingFunctionScore>,
122    #[serde(default)]
123    pub discovered: u64,
124    #[serde(default)]
125    pub library: u64,
126}
127
128/// FLOSS 里的“是否启用某类字符串”的开关。
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
130#[serde(transparent)]
131pub struct Enabled(pub bool);
132
133impl Enabled {
134    #[must_use]
135    pub const fn get(self) -> bool {
136        self.0
137    }
138}
139
140impl From<Enabled> for bool {
141    fn from(value: Enabled) -> Self {
142        value.0
143    }
144}
145
146/// FLOSS 分析配置与统计。
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148pub struct Analysis {
149    #[serde(default = "default_enabled")]
150    pub enable_decoded_strings: Enabled,
151    #[serde(default = "default_enabled")]
152    pub enable_stack_strings: Enabled,
153    #[serde(default = "default_enabled")]
154    pub enable_static_strings: Enabled,
155    #[serde(default = "default_enabled")]
156    pub enable_tight_strings: Enabled,
157    #[serde(default)]
158    pub functions: Functions,
159}
160
161/// FLOSS 输出元数据。
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct Metadata {
164    pub file_path: String,
165    #[serde(default)]
166    pub imagebase: Address,
167    #[serde(default)]
168    pub language: String,
169    #[serde(default)]
170    pub language_selected: String,
171    #[serde(default)]
172    pub language_version: String,
173    #[serde(default)]
174    pub min_length: u64,
175    #[serde(default)]
176    pub runtime: Runtime,
177    #[serde(default)]
178    pub version: String,
179}
180
181/// 字符串列表集合。
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183#[derive(Eq)]
184pub struct Strings {
185    #[serde(default)]
186    pub decoded_strings: Vec<DecodedString>,
187    #[serde(default)]
188    pub language_strings: Vec<StaticString>,
189    #[serde(default)]
190    pub language_strings_missed: Vec<StaticString>,
191    #[serde(default)]
192    pub stack_strings: Vec<StackString>,
193    #[serde(default)]
194    pub static_strings: Vec<StaticString>,
195    #[serde(default)]
196    pub tight_strings: Vec<StackString>,
197}
198
199const fn default_enabled() -> Enabled {
200    Enabled(true)
201}
202
203fn deserialize_address_key_map<'de, D>(
204    deserializer: D,
205) -> Result<BTreeMap<Address, DecodingFunctionScore>, D::Error>
206where
207    D: serde::Deserializer<'de>,
208{
209    struct MapVisitor;
210
211    impl<'de> serde::de::Visitor<'de> for MapVisitor {
212        type Value = BTreeMap<Address, DecodingFunctionScore>;
213
214        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215            formatter.write_str("包含十进制或 0x 十六进制地址 key 的 map")
216        }
217
218        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
219        where
220            M: serde::de::MapAccess<'de>,
221        {
222            let mut map = BTreeMap::new();
223            while let Some((key, value)) =
224                access.next_entry::<AddressKey, DecodingFunctionScore>()?
225            {
226                map.insert(key.0, value);
227            }
228            Ok(map)
229        }
230    }
231
232    deserializer.deserialize_map(MapVisitor)
233}
234
235struct AddressKey(Address);
236
237impl<'de> Deserialize<'de> for AddressKey {
238    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239    where
240        D: serde::Deserializer<'de>,
241    {
242        struct KeyVisitor;
243
244        impl serde::de::Visitor<'_> for KeyVisitor {
245            type Value = AddressKey;
246
247            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248                formatter.write_str("十进制或 0x 十六进制地址")
249            }
250
251            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
252            where
253                E: serde::de::Error,
254            {
255                Ok(AddressKey(value))
256            }
257
258            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
259            where
260                E: serde::de::Error,
261            {
262                u64::try_from(value)
263                    .map(AddressKey)
264                    .map_err(|_error| E::custom("期望非负整数地址"))
265            }
266
267            fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
268            where
269                E: serde::de::Error,
270            {
271                if value.is_finite() && value.fract() == 0.0 && value >= 0.0 {
272                    value
273                        .to_string()
274                        .parse::<u64>()
275                        .map(AddressKey)
276                        .map_err(|_error| E::custom("期望非负整数地址"))
277                } else {
278                    Err(E::custom("期望非负整数地址"))
279                }
280            }
281
282            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
283            where
284                E: serde::de::Error,
285            {
286                parse_address_key(value)
287                    .map(AddressKey)
288                    .map_err(E::custom)
289            }
290
291            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
292            where
293                E: serde::de::Error,
294            {
295                self.visit_str(&value)
296            }
297        }
298
299        deserializer.deserialize_any(KeyVisitor)
300    }
301}
302
303fn parse_address_key(value: &str) -> Result<Address, &'static str> {
304    if let Some(hex) = value.strip_prefix("0x").or_else(|| value.strip_prefix("0X")) {
305        if hex.is_empty() {
306            return Err("十六进制地址不能为空");
307        }
308        u64::from_str_radix(hex, 16).map_err(|_error| "无效的十六进制地址")
309    } else if value.is_empty() {
310        Err("地址不能为空")
311    } else {
312        value.parse::<u64>().map_err(|_error| "无效的十进制地址")
313    }
314}
315
316fn deserialize_u64_from_number<'de, D>(deserializer: D) -> Result<u64, D::Error>
317where
318    D: serde::Deserializer<'de>,
319{
320    struct NumberVisitor;
321
322    impl serde::de::Visitor<'_> for NumberVisitor {
323        type Value = u64;
324
325        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326            formatter.write_str("a non-negative integer")
327        }
328
329        fn visit_u64<E>(self, value: u64) -> Result<u64, E>
330        where
331            E: serde::de::Error,
332        {
333            Ok(value)
334        }
335
336        fn visit_i64<E>(self, value: i64) -> Result<u64, E>
337        where
338            E: serde::de::Error,
339        {
340            u64::try_from(value).map_err(|_error| E::custom("expected a non-negative integer"))
341        }
342
343        fn visit_f64<E>(self, value: f64) -> Result<u64, E>
344        where
345            E: serde::de::Error,
346        {
347            if value.is_finite() && value.fract() == 0.0 && value >= 0.0 {
348                value
349                    .to_string()
350                    .parse::<u64>()
351                    .map_err(|_error| E::custom("expected a non-negative integer"))
352            } else {
353                Err(E::custom("expected a non-negative integer"))
354            }
355        }
356
357        fn visit_str<E>(self, value: &str) -> Result<u64, E>
358        where
359            E: serde::de::Error,
360        {
361            value
362                .parse::<u64>()
363                .map_err(|_error| E::custom("expected a non-negative integer"))
364        }
365    }
366
367    deserializer.deserialize_any(NumberVisitor)
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn decoding_function_scores_accepts_hex_keys() {
376        let json = r#"{"analysis":{"functions":{"decoding_function_scores":{"0x1000":{"score":1.5,"xrefs_to":2}}}},"metadata":{"file_path":"sample.bin"},"strings":{}}"#;
377        let doc: ResultDocument = serde_json::from_str(json).expect("解析 JSON 失败");
378        let mut expected = BTreeMap::new();
379        expected.insert(
380            0x1000,
381            DecodingFunctionScore {
382                score: 1.5,
383                xrefs_to: 2,
384            },
385        );
386        assert_eq!(doc.analysis.functions.decoding_function_scores, expected);
387    }
388
389    #[test]
390    fn decoding_function_scores_accepts_decimal_keys() {
391        let json = r#"{"analysis":{"functions":{"decoding_function_scores":{"4096":{"score":1.5,"xrefs_to":2}}}},"metadata":{"file_path":"sample.bin"},"strings":{}}"#;
392        let doc: ResultDocument = serde_json::from_str(json).expect("解析 JSON 失败");
393        assert!(doc
394            .analysis
395            .functions
396            .decoding_function_scores
397            .contains_key(&4096));
398    }
399}