threatflux_binary_analysis/formats/
wasm.rs

1//! WebAssembly (Wasm) format parser
2
3use crate::{
4    types::{
5        Architecture, BinaryFormat as Format, BinaryMetadata, Endianness, Export, Import, Section,
6        SectionPermissions, SectionType, SecurityFeatures, Symbol,
7    },
8    BinaryFormatParser, BinaryFormatTrait, Result,
9};
10
11use wasmparser::{Parser, Payload};
12
13/// WebAssembly format parser
14pub struct WasmParser;
15
16impl BinaryFormatParser for WasmParser {
17    fn parse(data: &[u8]) -> Result<Box<dyn BinaryFormatTrait>> {
18        Ok(Box::new(WasmBinary::parse(data)?))
19    }
20
21    fn can_parse(data: &[u8]) -> bool {
22        data.len() >= 4 && &data[0..4] == b"\0asm"
23    }
24}
25
26/// Parsed WebAssembly binary
27pub struct WasmBinary {
28    #[allow(dead_code)]
29    data: Vec<u8>,
30    metadata: BinaryMetadata,
31    sections: Vec<Section>,
32    imports: Vec<Import>,
33    exports: Vec<Export>,
34}
35
36impl WasmBinary {
37    fn parse(data: &[u8]) -> Result<Self> {
38        let parser = Parser::new(0);
39        let mut sections = Vec::new();
40        let mut imports = Vec::new();
41        let mut exports = Vec::new();
42        let mut start_fn: Option<u64> = None;
43
44        for payload in parser.parse_all(data) {
45            let payload = payload?;
46            match payload {
47                Payload::Version { .. } => {}
48                Payload::StartSection { func, .. } => {
49                    start_fn = Some(func as u64);
50                }
51                Payload::ImportSection(s) => {
52                    let range = s.range();
53                    for import in s {
54                        let import = import?;
55                        imports.push(Import {
56                            name: import.name.to_string(),
57                            library: Some(import.module.to_string()),
58                            address: None,
59                            ordinal: None,
60                        });
61                    }
62                    sections.push(Section {
63                        name: "import".to_string(),
64                        address: 0,
65                        size: (range.end - range.start) as u64,
66                        offset: range.start as u64,
67                        permissions: SectionPermissions {
68                            read: true,
69                            write: false,
70                            execute: false,
71                        },
72                        section_type: SectionType::Other("Import".to_string()),
73                        data: None,
74                    });
75                }
76                Payload::ExportSection(s) => {
77                    let range = s.range();
78                    for export in s {
79                        let export = export?;
80                        exports.push(Export {
81                            name: export.name.to_string(),
82                            address: 0,
83                            ordinal: None,
84                            forwarded_name: None,
85                        });
86                    }
87                    sections.push(Section {
88                        name: "export".to_string(),
89                        address: 0,
90                        size: (range.end - range.start) as u64,
91                        offset: range.start as u64,
92                        permissions: SectionPermissions {
93                            read: true,
94                            write: false,
95                            execute: false,
96                        },
97                        section_type: SectionType::Other("Export".to_string()),
98                        data: None,
99                    });
100                }
101                Payload::CodeSectionStart { range, .. } => {
102                    sections.push(Section {
103                        name: "code".to_string(),
104                        address: 0,
105                        size: (range.end - range.start) as u64,
106                        offset: range.start as u64,
107                        permissions: SectionPermissions {
108                            read: true,
109                            write: false,
110                            execute: true,
111                        },
112                        section_type: SectionType::Code,
113                        data: None,
114                    });
115                }
116                Payload::DataSection(s) => {
117                    let range = s.range();
118                    // Consume section entries
119                    for _ in s {} // iterating to ensure parser advances
120                    sections.push(Section {
121                        name: "data".to_string(),
122                        address: 0,
123                        size: (range.end - range.start) as u64,
124                        offset: range.start as u64,
125                        permissions: SectionPermissions {
126                            read: true,
127                            write: true,
128                            execute: false,
129                        },
130                        section_type: SectionType::Data,
131                        data: None,
132                    });
133                }
134                Payload::CustomSection(section) => {
135                    let name = section.name().to_string();
136                    sections.push(Section {
137                        name: name.clone(),
138                        address: 0,
139                        size: section.data().len() as u64,
140                        offset: section.data_offset() as u64,
141                        permissions: SectionPermissions {
142                            read: true,
143                            write: false,
144                            execute: false,
145                        },
146                        section_type: SectionType::Other(name),
147                        data: None,
148                    });
149                }
150                _ => {}
151            }
152        }
153
154        let metadata = BinaryMetadata {
155            size: data.len(),
156            format: Format::Wasm,
157            architecture: Architecture::Wasm,
158            entry_point: start_fn,
159            base_address: None,
160            timestamp: None,
161            compiler_info: None,
162            endian: Endianness::Little,
163            security_features: SecurityFeatures::default(),
164        };
165
166        Ok(Self {
167            data: data.to_vec(),
168            metadata,
169            sections,
170            imports,
171            exports,
172        })
173    }
174}
175
176impl BinaryFormatTrait for WasmBinary {
177    fn format_type(&self) -> Format {
178        Format::Wasm
179    }
180
181    fn architecture(&self) -> Architecture {
182        Architecture::Wasm
183    }
184
185    fn entry_point(&self) -> Option<u64> {
186        self.metadata.entry_point
187    }
188
189    fn sections(&self) -> &[Section] {
190        &self.sections
191    }
192
193    fn symbols(&self) -> &[Symbol] {
194        &[]
195    }
196
197    fn imports(&self) -> &[Import] {
198        &self.imports
199    }
200
201    fn exports(&self) -> &[Export] {
202        &self.exports
203    }
204
205    fn metadata(&self) -> &BinaryMetadata {
206        &self.metadata
207    }
208}