Before

1//! Primary Binwalk interface.
2
3use aho_corasick::AhoCorasick;
4use log::{debug, error, info, warn};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path;
9use uuid::Uuid;
10
11#[cfg(windows)]
12use std::os::windows;
13
14#[cfg(unix)]
15use std::os::unix;
16
17use crate::common::{is_offset_safe, read_file};
18use crate::extractors;
19use crate::magic;
20use crate::signatures;
21
22/// Returned on initialization error
23#[derive(Debug, Default, Clone)]
24pub struct BinwalkError {
25 pub message: String,
26}
27
28impl BinwalkError {
29 pub fn new(message: &str) -> Self {
30 BinwalkError {
31 message: message.to_string(),
32 }
33 }
34}
35
36/// Analysis results returned by Binwalk::analyze
37#[derive(Debug, Default, Clone, Serialize, Deserialize)]
38pub struct AnalysisResults {
39 /// Path to the file that was analyzed
40 pub file_path: String,
41 /// File signature results, as returned by Binwalk::scan
42 pub file_map: Vec<signatures::common::SignatureResult>,
43 /// File extraction results, as returned by Binwalk::extract.
44 /// HashMap key is the corresponding SignatureResult.id value in `file_map`.
45 pub extractions: HashMap<String, extractors::common::ExtractionResult>,
46}
47
48/// Analyze files / memory for file signatures
49///
50/// ## Example
51///
52/// ```
53/// use binwalk::Binwalk;
54///
55/// let target_file = "/bin/ls";
56/// let data_to_scan = std::fs::read(target_file).expect("Unable to read file");
57///
58/// let binwalker = Binwalk::new();
59///
60/// let signature_results = binwalker.scan(&data_to_scan);
61///
62/// for result in &signature_results {
63/// println!("Found '{}' at offset {:#X}", result.description, result.offset);
64/// }
65/// ```
66#[derive(Debug, Default, Clone)]
67pub struct Binwalk {
68 /// Count of all signatures (short and regular)
69 pub signature_count: usize,
70 /// Count of all magic patterns (short and regular)
71 pub pattern_count: usize,
72 /// The base file requested for analysis
73 pub base_target_file: String,
74 /// The base output directory for extracted files
75 pub base_output_directory: String,
76 /// A list of signatures that must start at offset 0
77 pub short_signatures: Vec<signatures::common::Signature>,
78 /// A list of magic bytes to search for throughout the entire file
79 pub patterns: Vec<Vec<u8>>,
80 /// Maps patterns to their corresponding signature
81 pub pattern_signature_table: HashMap<usize, signatures::common::Signature>,
82 /// Maps signatures to their corresponding extractors
83 pub extractor_lookup_table: HashMap<String, Option<extractors::common::Extractor>>,
84}
85
86impl Binwalk {
87 /// Create a new Binwalk instance with all default values.
88 /// Equivalent to `Binwalk::configure(None, None, None, None, None, false)`.
89 ///
90 /// ## Example
91 ///
92 /// ```
93 /// use binwalk::Binwalk;
94 ///
95 /// let binwalker = Binwalk::new();
96 /// ```
97 #[allow(dead_code)]
98 pub fn new() -> Binwalk {
99 Binwalk::configure(None, None, None, None, None, false).unwrap()
100 }
101
102 /// Create a new Binwalk instance.
103 ///
104 /// If `target_file_name` and `output_directory` are specified, the `output_directory` will be created if it does not
105 /// already exist, and a symlink to `target_file_name` will be placed inside the `output_directory`. The path to this
106 /// symlink is placed in `Binwalk.base_target_file`.
107 ///
108 /// The `include` and `exclude` arguments specify include and exclude signature filters. The String values contained
109 /// in these arguments must match the `Signature.name` values defined in magic.rs.
110 ///
111 /// Additional user-defined signatures may be provided via the `signatures` argument.
112 ///
113 /// ## Example
114 ///
115 /// ```
116 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_102_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
117 /// use binwalk::Binwalk;
118 ///
119 /// // Don't scan for these file signatures
120 /// let exclude_filters: Vec<String> = vec!["jpeg".to_string(), "png".to_string()];
121 ///
122 /// let binwalker = Binwalk::configure(None,
123 /// None,
124 /// None,
125 /// Some(exclude_filters),
126 /// None,
127 /// false)?;
128 /// # Ok(binwalker)
129 /// # } _doctest_main_src_binwalk_rs_102_0(); }
130 /// ```
131 pub fn configure(
132 target_file_name: Option<String>,
133 output_directory: Option<String>,
134 include: Option<Vec<String>>,
135 exclude: Option<Vec<String>>,
136 signatures: Option<Vec<signatures::common::Signature>>,
137 full_search: bool,
138 ) -> Result<Binwalk, BinwalkError> {
139 let mut new_instance = Binwalk {
140 ..Default::default()
141 };
142
143 // Target file is optional, especially if being called via the library
144 if let Some(target_file) = target_file_name {
145 // Set the target file path, make it an absolute path
146 match path::absolute(&target_file) {
147 Err(_) => {
148 return Err(BinwalkError::new(&format!(
149 "Failed to get absolute path for '{target_file}'"
150 )));
151 }
152 Ok(abspath) => {
153 new_instance.base_target_file = abspath.display().to_string();
154 }
155 }
156
157 // If an output extraction directory was also specified, initialize it
158 if let Some(extraction_directory) = output_directory {
159 // Make the extraction directory an absolute path
160 match path::absolute(&extraction_directory) {
161 Err(_) => {
162 return Err(BinwalkError::new(&format!(
163 "Failed to get absolute path for '{extraction_directory}'"
164 )));
165 }
166 Ok(abspath) => {
167 new_instance.base_output_directory = abspath.display().to_string();
168 }
169 }
170
171 // Initialize the extraction directory. This will create the directory if it
172 // does not exist, and create a symlink inside the directory that points to
173 // the specified target file.
174 match init_extraction_directory(
175 &new_instance.base_target_file,
176 &new_instance.base_output_directory,
177 ) {
178 Err(e) => {
179 return Err(BinwalkError::new(&format!(
180 "Failed to initialize extraction directory: {e}"
181 )));
182 }
183 Ok(new_target_file_path) => {
184 // This is the new base target path (a symlink inside the extraction directory)
185 new_instance.base_target_file = new_target_file_path.clone();
186 }
187 }
188 }
189 }
190
191 // Load all internal signature patterns
192 let mut signature_patterns = magic::patterns();
193
194 // Include any user-defined signature patterns
195 if let Some(user_defined_signature_patterns) = signatures {
196 signature_patterns.extend(user_defined_signature_patterns);
197 }
198
199 // Load magic signatures
200 for signature in signature_patterns.clone() {
201 // Check if this signature should be included
202 if !include_signature(&signature, &include, &exclude) {
203 continue;
204 }
205
206 // Keep a count of total unique signatures that are supported
207 new_instance.signature_count += 1;
208
209 // Keep a count of the total number of magic patterns
210 new_instance.pattern_count += signature.magic.len();
211
212 // Create a lookup table which associates each signature to its respective extractor
213 new_instance
214 .extractor_lookup_table
215 .insert(signature.name.clone(), signature.extractor.clone());
216
217 // Each signature may have multiple magic bytes associated with it
218 for pattern in signature.magic.clone() {
219 if signature.short && !full_search {
220 // These are short patterns, and should only be searched for at the very beginning of a file
221 new_instance.short_signatures.push(signature.clone());
222 break;
223 } else {
224 /*
225 * Need to keep a mapping of the pattern index and its associated signature
226 * so that when a match is found it can be resolved back to the signature from
227 * which it came.
228 */
229 new_instance
230 .pattern_signature_table
231 .insert(new_instance.patterns.len(), signature.clone());
232
233 // Add these magic bytes to the list of patterns
234 new_instance.patterns.push(pattern.to_vec());
235 }
236 }
237 }
238
239 Ok(new_instance)
240 }
241
242 /// Scan a file for magic signatures.
243 /// Returns a list of validated magic signatures representing the known contents of the file.
244 ///
245 /// ## Example
246 ///
247 /// ```
248 /// use binwalk::Binwalk;
249 ///
250 /// let target_file = "/bin/ls";
251 /// let data_to_scan = std::fs::read(target_file).expect("Unable to read file");
252 ///
253 /// let binwalker = Binwalk::new();
254 ///
255 /// let signature_results = binwalker.scan(&data_to_scan);
256 ///
257 /// for result in &signature_results {
258 /// println!("{:#X} {}", result.offset, result.description);
259 /// }
260 ///
261 /// assert!(signature_results.len() > 0);
262 /// ```
263 pub fn scan(&self, file_data: &[u8]) -> Vec<signatures::common::SignatureResult> {
264 const FILE_START_OFFSET: usize = 0;
265
266 let mut index_adjustment: usize = 0;
267 let mut next_valid_offset: usize = 0;
268 let mut previous_valid_offset = None;
269
270 let available_data = file_data.len();
271
272 // A list of identified signatures, representing a "map" of the file data
273 let mut file_map: Vec<signatures::common::SignatureResult> = vec![];
274
275 /*
276 * Check beginning of file for short signatures.
277 * These signatures are only valid if they occur at the very beginning of a file.
278 * This is typically because the signatures are very short and they are likely
279 * to occur randomly throughout the file, so this prevents having to validate many
280 * false positve matches.
281 */
282 for signature in &self.short_signatures {
283 for magic in signature.magic.clone() {
284 let magic_start = FILE_START_OFFSET + signature.magic_offset;
285 let magic_end = magic_start + magic.len();
286
287 if file_data.len() > magic_end && file_data[magic_start..magic_end] == magic {
288 debug!(
289 "Found {} short magic match at offset {:#X}",
290 signature.description, magic_start
291 );
292
293 if let Ok(mut signature_result) = (signature.parser)(file_data, magic_start) {
294 // Auto populate some signature result fields
295 signature_result_auto_populate(&mut signature_result, signature);
296
297 // Add this signature to the file map
298 file_map.push(signature_result.clone());
299 info!(
300 "Found valid {} short signature at offset {:#X}",
301 signature_result.name, FILE_START_OFFSET
302 );
303
304 // Only update the next_valid_offset if confidence is high; these are, after all, short signatures
305 if signature_result.confidence >= signatures::common::CONFIDENCE_HIGH {
306 next_valid_offset = signature_result.offset + signature_result.size;
307 }
308
309 // Only one signature can match at fixed offset 0
310 break;
311 } else {
312 debug!(
313 "{} short signature match at offset {:#X} is invalid",
314 signature.description, FILE_START_OFFSET
315 );
316 }
317 }
318 }
319 }
320
321 /*
322 * Same pattern matching algorithm used by fgrep.
323 * This will search for all magic byte patterns in the file data, all at once.
324 * https://en.wikipedia.org/wiki/Aho–Corasick_algorithm
325 */
326 let grep = AhoCorasick::new(self.patterns.clone()).unwrap();
327
328 debug!("Running Aho-Corasick scan");
329
330 /*
331 * Outer loop wrapper for AhoCorasick scan loop. This will loop until:
332 *
333 * 1) next_valid_offset exceeds available_data
334 * 2) previous_valid_offset <= next_valid_offset
335 */
336 while is_offset_safe(available_data, next_valid_offset, previous_valid_offset) {
337 // Update the previous valid offset in praparation for the next loop iteration
338 previous_valid_offset = Some(next_valid_offset);
339
340 debug!("Continuing scan from offset {next_valid_offset:#X}");
341
342 /*
343 * Run a new AhoCorasick scan starting at the next valid offset in the file data.
344 * This will loop until:
345 *
346 * 1) All data has been exhausted, in which case previous_valid_offset and next_valid_offset
347 * will be identical, causing the outer while loop to break.
348 * 2) A valid signature with a defined size is found, in which case next_valid_offset will
349 * be updated to point the end of the valid signature data, causing a new AhoCorasick
350 * scan to start at the new next_valid_offset file location.
351 */
352 for magic_match in grep.find_overlapping_iter(&file_data[next_valid_offset..]) {
353 // Get the location of the magic bytes inside the file data
354 let magic_offset: usize = next_valid_offset + magic_match.start();
355
356 // Get the signature associated with this magic signature
357 let magic_pattern_index: usize = magic_match.pattern().as_usize();
358 let signature: signatures::common::Signature = self
359 .pattern_signature_table
360 .get(&magic_pattern_index)
361 .unwrap()
362 .clone();
363
364 debug!(
365 "Found {} magic match at offset {:#X}",
366 signature.description, magic_offset
367 );
368
369 /*
370 * Invoke the signature parser to parse and validate the signature.
371 * An error indicates a false positive match for the signature type.
372 */
373 if let Ok(mut signature_result) = (signature.parser)(file_data, magic_offset) {
374 // Calculate the end of this signature's data
375 let signature_end_offset = signature_result.offset + signature_result.size;
376
377 // Sanity check the reported offset and size vs file size
378 if signature_end_offset > available_data {
379 info!("Signature {} extends beyond EOF; ignoring", signature.name);
380 // Continue inner loop
381 continue;
382 }
383
384 // Auto populate some signature result fields
385 signature_result_auto_populate(&mut signature_result, &signature);
386
387 // Add this signature to the file map
388 file_map.push(signature_result.clone());
389
390 info!(
391 "Found valid {} signature at offset {:#X}",
392 signature_result.name, signature_result.offset
393 );
394
395 // Only update the next_valid_offset if confidence is at least medium
396 if signature_result.confidence >= signatures::common::CONFIDENCE_MEDIUM {
397 // Only update the next_valid offset if the end of the signature reported the size of its contents
398 if signature_result.size > 0 {
399 // This file's signature has a known size, so there's no need to scan inside this file's data.
400 // Update next_valid_offset to point to the end of this file signature and break out of the
401 // inner loop.
402 next_valid_offset = signature_end_offset;
403 break;
404 }
405 }
406 } else {
407 debug!(
408 "{} magic match at offset {:#X} is invalid",
409 signature.description, magic_offset
410 );
411 }
412 }
413 }
414
415 debug!("Aho-Corasick scan found {} magic matches", file_map.len());
416
417 /*
418 * A file's magic bytes do not always start at the beginning of a file, meaning that it is possible
419 * that the order in which the signatures were found in the file data is not the order in which we
420 * want to process/validate the signatures. Each signature's parser function will report the correct
421 * starting offset for the signature, so sort the file_map by the SignatureResult.offset value.
422 */
423 file_map.sort();
424 next_valid_offset = 0;
425
426 /*
427 * Now that signatures are in the correct order, identify and any overlapping signatures
428 * (such as gzip files identified within a tarball archive), signatures with the same reported offset,
429 * and any signatures with an invalid reported size (i.e., the size extends beyond the end of available file_data).
430 */
431 for mut i in 0..file_map.len() {
432 // Some entries may have been removed from the file_map list in previous loop iterations; adjust the index accordingly
433 i -= index_adjustment;
434
435 // Make sure the file map index is valid
436 if file_map.is_empty() || i >= file_map.len() {
437 break;
438 }
439
440 let this_signature = file_map[i].clone();
441 let remaining_available_size = file_data.len() - this_signature.offset;
442
443 // Check if the previous file map entry had the same reported starting offset as this one
444 if i > 0 && this_signature.offset == file_map[i - 1].offset {
445 // Get the previous signature in the file map
446 let previous_signature = file_map[i - 1].clone();
447
448 // If this file map entry and the conflicting entry do not have the same confidence level, default to the one with highest confidence
449 if this_signature.confidence != previous_signature.confidence {
450 debug!(
451 "Conflicting signatures at offset {:#X}; defaulting to the signature with highest confidence",
452 this_signature.offset
453 );
454
455 // If this signature is higher confidence, invalidate the previous signature
456 if this_signature.confidence > previous_signature.confidence {
457 file_map.remove(i - 1);
458 index_adjustment += 1;
459
460 // Else, this signature has a lower confidence; invalidate this signature and continue to the next signature in the list
461 } else {
462 file_map.remove(i);
463 index_adjustment += 1;
464 continue;
465 }
466
467 // Conflicting signatures have identical confidence levels; defer to the previously vetted signature
468 } else {
469 debug!(
470 "Conflicting signatures at offset {:#X} with the same confidence; first come, first served",
471 this_signature.offset
472 );
473 file_map.remove(i);
474 index_adjustment += 1;
475 continue;
476 }
477
478 // Else, if the offsets don't conflict, make sure this signature doesn't fall inside a previously identified signature's data
479 } else if this_signature.offset < next_valid_offset {
480 debug!(
481 "Signature {} at offset {:#X} contains conflicting data; ignoring",
482 this_signature.name, this_signature.offset
483 );
484 file_map.remove(i);
485 index_adjustment += 1;
486 continue;
487 }
488
489 // If we've made it this far, make sure this signature's data doesn't extend beyond EOF and that the file data doesn't wrap around
490 if this_signature.size > remaining_available_size
491 || ((this_signature.offset + this_signature.size) as isize) < 0
492 {
493 debug!(
494 "Signature {} at offset {:#X} claims its size extends beyond EOF; ignoring",
495 this_signature.name, this_signature.offset
496 );
497 file_map.remove(i);
498 index_adjustment += 1;
499 continue;
500 }
501
502 // This signature looks OK, update the next_valid_offset to be the end of this signature's data, only if we're fairly confident in the signature
503 if this_signature.confidence >= signatures::common::CONFIDENCE_MEDIUM {
504 next_valid_offset = this_signature.offset + this_signature.size;
505 }
506 }
507
508 /*
509 * Ideally, all signatures would report their size; some file formats do not specify a size, and the only
510 * way to determine the size is to extract the file format (compressed data, for example).
511 * For signatures with a reported size of 0, update their size to be the start of the next signature, or EOF.
512 * This makes the assumption that there are no false positives or false negatives.
513 *
514 * False negatives (i.e., there is some other file format or data between this signature and the next that
515 * was not correctly identified) is less problematic, as this will overestimate the size of this signature,
516 * but most extraction utilities don't care about this extra trailing data being included.
517 *
518 * False positives (i.e., some data inside of this signature is identified as some other file type) can cause
519 * this signature's file data to become truncated, which will inevitably result in a failed, or partial, extraction.
520 *
521 * Thus, signatures must be very good at validating magic matches and eliminating false positives.
522 */
523 for i in 0..file_map.len() {
524 if file_map[i].size == 0 {
525 // Index of the next file map entry, if any
526 let next_index = i + 1;
527
528 // By default, assume this signature goes to EOF
529 let mut next_offset: usize = file_data.len();
530
531 // If there are more entries in the file map
532 if next_index < file_map.len() {
533 // Look through all remaining file map entries for one with medium to high confidence
534 for file_map_entry in file_map.iter().skip(next_index) {
535 if file_map_entry.confidence >= signatures::common::CONFIDENCE_MEDIUM {
536 // If a signature of at least medium confidence is found, assume that *this* signature ends there
537 next_offset = file_map_entry.offset;
538 break;
539 }
540 }
541 }
542
543 file_map[i].size = next_offset - file_map[i].offset;
544 warn!(
545 "Signature {}:{:#X} size is unknown; assuming size of {:#X} bytes",
546 file_map[i].name, file_map[i].offset, file_map[i].size
547 );
548 } else {
549 debug!(
550 "Signature {}:{:#X} has a reported size of {:#X} bytes",
551 file_map[i].name, file_map[i].offset, file_map[i].size
552 );
553 }
554 }
555
556 debug!("Found {} valid signatures", file_map.len());
557
558 file_map
559 }
560
561 /// Extract all extractable signatures found in a file.
562 ///
563 /// ## Example
564 ///
565 /// ```
566 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_529_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
567 /// use binwalk::Binwalk;
568 ///
569 /// let target_path = std::path::Path::new("tests")
570 /// .join("inputs")
571 /// .join("gzip.bin")
572 /// .display()
573 /// .to_string();
574 ///
575 /// let extraction_directory = std::path::Path::new("tests")
576 /// .join("extractions")
577 /// .display()
578 /// .to_string();
579 ///
580 /// # std::fs::remove_dir_all(&extraction_directory);
581 /// let binwalker = Binwalk::configure(Some(target_path),
582 /// Some(extraction_directory.clone()),
583 /// None,
584 /// None,
585 /// None,
586 /// false)?;
587 ///
588 /// let file_data = std::fs::read(&binwalker.base_target_file).expect("Unable to read file");
589 ///
590 /// let scan_results = binwalker.scan(&file_data);
591 /// let extraction_results = binwalker.extract(&file_data, &binwalker.base_target_file, &scan_results);
592 ///
593 /// assert_eq!(scan_results.len(), 1);
594 /// assert_eq!(extraction_results.len(), 1);
595 /// assert_eq!(std::path::Path::new(&extraction_directory)
596 /// .join("gzip.bin.extracted")
597 /// .join("0")
598 /// .join("decompressed.bin")
599 /// .exists(), true);
600 /// # std::fs::remove_dir_all(&extraction_directory);
601 /// # Ok(binwalker)
602 /// # } _doctest_main_src_binwalk_rs_529_0(); }
603 /// ```
604 pub fn extract(
605 &self,
606 file_data: &[u8],
607 file_name: impl Into<String>,
608 file_map: &Vec<signatures::common::SignatureResult>,
609 ) -> HashMap<String, extractors::common::ExtractionResult> {
610 let file_path = file_name.into();
611 let mut extraction_results: HashMap<String, extractors::common::ExtractionResult> =
612 HashMap::new();
613
614 // Spawn extractors for each extractable signature
615 for signature in file_map {
616 // Signatures may opt to not perform extraction; honor this request
617 if signature.extraction_declined {
618 continue;
619 }
620
621 // Get the extractor for this signature
622 let extractor = self.extractor_lookup_table[&signature.name].clone();
623
624 match &extractor {
625 None => continue,
626 Some(_) => {
627 // Run an extraction for this signature
628 let mut extraction_result =
629 extractors::common::execute(file_data, &file_path, signature, &extractor);
630
631 if !extraction_result.success {
632 debug!(
633 "Extraction failed for {} (ID: {}) {:#X} - {:#X}",
634 signature.name, signature.id, signature.offset, signature.size
635 );
636
637 // Calculate all available data from the start of this signature to EOF
638 let available_data = file_data.len() - signature.offset;
639
640 /*
641 * If extraction failed, it could be due to truncated data (signature matching is not perfect ya know!)
642 * In that case, make one more attempt, this time provide the extractor all the data possible.
643 */
644 if signature.size < available_data {
645 // Create a duplicate signature, but set its reported size to the length of all available data
646 let mut new_signature = signature.clone();
647 new_signature.size = available_data;
648
649 debug!(
650 "Trying extraction for {} (ID: {}) again, this time from {:#X} - {:#X}",
651 new_signature.name,
652 new_signature.id,
653 new_signature.offset,
654 new_signature.size
655 );
656
657 // Re-run the extraction
658 extraction_result = extractors::common::execute(
659 file_data,
660 &file_path,
661 &new_signature,
662 &extractor,
663 );
664 }
665 }
666
667 // Update the HashMap with the result of this extraction attempt
668 extraction_results.insert(signature.id.clone(), extraction_result);
669 }
670 }
671 }
672
673 extraction_results
674 }
675
676 /// Analyze a data buffer and optionally extract the file contents.
677 ///
678 /// ## Example
679 ///
680 /// ```
681 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
682 /// use binwalk::{Binwalk, common};
683 ///
684 /// let target_path = std::path::Path::new("tests")
685 /// .join("inputs")
686 /// .join("gzip.bin")
687 /// .display()
688 /// .to_string();
689 ///
690 /// let extraction_directory = std::path::Path::new("tests")
691 /// .join("extractions")
692 /// .display()
693 /// .to_string();
694 ///
695 /// let file_data = common::read_file(&target_path).expect("Failed to read file data");
696 ///
697 /// # std::fs::remove_dir_all(&extraction_directory);
698 /// let binwalker = Binwalk::configure(Some(target_path),
699 /// Some(extraction_directory.clone()),
700 /// None,
701 /// None,
702 /// None,
703 /// false)?;
704 ///
705 /// let analysis_results = binwalker.analyze_buf(&file_data, &binwalker.base_target_file, true);
706 ///
707 /// assert_eq!(analysis_results.file_map.len(), 1);
708 /// assert_eq!(analysis_results.extractions.len(), 1);
709 /// assert_eq!(std::path::Path::new(&extraction_directory)
710 /// .join("gzip.bin.extracted")
711 /// .join("0")
712 /// .join("decompressed.bin")
713 /// .exists(), true);
714 /// # std::fs::remove_dir_all(&extraction_directory);
715 /// # Ok(binwalker)
716 /// # } _doctest_main_src_binwalk_rs_672_0(); }
717 /// ```
718 pub fn analyze_buf(
719 &self,
720 file_data: &[u8],
721 target_file: impl Into<String>,
722 do_extraction: bool,
723 ) -> AnalysisResults {
724 let file_path = target_file.into();
725
726 // Return value
727 let mut results: AnalysisResults = AnalysisResults {
728 file_path: file_path.clone(),
729 ..Default::default()
730 };
731
732 // Scan file data for signatures
733 debug!("Analysis start: {file_path}");
734 results.file_map = self.scan(file_data);
735
736 // Only extract if told to, and if there were some signatures found in this file
737 if do_extraction && !results.file_map.is_empty() {
738 // Extract everything we can
739 debug!(
740 "Submitting {} signature results to extractor",
741 results.file_map.len()
742 );
743 results.extractions = self.extract(file_data, &file_path, &results.file_map);
744 }
745
746 debug!("Analysis end: {file_path}");
747
748 results
749 }
750
751 /// Analyze a file on disk and optionally extract its contents.
752 ///
753 /// ## Example
754 ///
755 /// ```
756 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_745_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
757 /// use binwalk::Binwalk;
758 ///
759 /// let target_path = std::path::Path::new("tests")
760 /// .join("inputs")
761 /// .join("gzip.bin")
762 /// .display()
763 /// .to_string();
764 ///
765 /// let extraction_directory = std::path::Path::new("tests")
766 /// .join("extractions")
767 /// .display()
768 /// .to_string();
769 ///
770 /// # std::fs::remove_dir_all(&extraction_directory);
771 /// let binwalker = Binwalk::configure(Some(target_path),
772 /// Some(extraction_directory.clone()),
773 /// None,
774 /// None,
775 /// None,
776 /// false)?;
777 ///
778 /// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true);
779 ///
780 /// assert_eq!(analysis_results.file_map.len(), 1);
781 /// assert_eq!(analysis_results.extractions.len(), 1);
782 /// assert_eq!(std::path::Path::new(&extraction_directory)
783 /// .join("gzip.bin.extracted")
784 /// .join("0")
785 /// .join("decompressed.bin")
786 /// .exists(), true);
787 /// # std::fs::remove_dir_all(&extraction_directory);
788 /// # Ok(binwalker)
789 /// # } _doctest_main_src_binwalk_rs_745_0(); }
790 /// ```
791 #[allow(dead_code)]
792 pub fn analyze(&self, target_file: impl Into<String>, do_extraction: bool) -> AnalysisResults {
793 let file_path = target_file.into();
794
795 let file_data = match read_file(&file_path) {
796 Err(_) => {
797 error!("Failed to read data from {file_path}");
798 b"".to_vec()
799 }
800 Ok(data) => data,
801 };
802
803 self.analyze_buf(&file_data, &file_path, do_extraction)
804 }
805}
806
807/// Initializes the extraction output directory
808fn init_extraction_directory(
809 target_file: &str,
810 extraction_directory: &str,
811) -> Result<String, std::io::Error> {
812 // Create the output directory, equivalent of mkdir -p
813 match fs::create_dir_all(extraction_directory) {
814 Ok(_) => {
815 debug!("Created base output directory: '{extraction_directory}'");
816 }
817 Err(e) => {
818 error!(
819 "Failed to create base output directory '{extraction_directory}': {e}"
820 );
821 return Err(e);
822 }
823 }
824
825 // Create a Path for the target file
826 let target_path = path::Path::new(&target_file);
827
828 // Build a symlink path to the target file in the extraction directory
829 let link_target_path_str = format!(
830 "{}{}{}",
831 extraction_directory,
832 path::MAIN_SEPARATOR,
833 target_path.file_name().unwrap().to_str().unwrap()
834 );
835
836 // Create a path for the symlink target path
837 let link_path = path::Path::new(&link_target_path_str);
838
839 if link_path.exists() {
840 return Ok(link_target_path_str);
841 }
842
843 debug!(
844 "Creating symlink from {} -> {}",
845 link_path.display(),
846 target_path.display()
847 );
848
849 // Create a symlink from inside the extraction directory to the specified target file
850 #[cfg(unix)]
851 {
852 match unix::fs::symlink(target_path, link_path) {
853 Ok(_) => Ok(link_target_path_str),
854 Err(e) => {
855 error!(
856 "Failed to create symlink {} -> {}: {}",
857 link_path.display(),
858 target_path.display(),
859 e
860 );
861 Err(e)
862 }
863 }
864 }
865 #[cfg(windows)]
866 {
867 match std::fs::hard_link(target_path, link_path) {
868 Ok(_) => {
869 return Ok(link_target_path_str);
870 }
871 Err(e) => {
872 error!(
873 "Failed to create hardlink {} -> {}: {}",
874 link_path.display(),
875 target_path.display(),
876 e
877 );
878 return Err(e);
879 }
880 }
881 }
882}
883
884/// Returns true if the signature should be included for file analysis, else returns false.
885fn include_signature(
886 signature: &signatures::common::Signature,
887 include: &Option<Vec<String>>,
888 exclude: &Option<Vec<String>>,
889) -> bool {
890 if let Some(include_signatures) = include {
891 for include_str in include_signatures {
892 if signature.name.to_lowercase() == include_str.to_lowercase() {
893 return true;
894 }
895 }
896
897 return false;
898 }
899
900 if let Some(exclude_signatures) = exclude {
901 for exclude_str in exclude_signatures {
902 if signature.name.to_lowercase() == exclude_str.to_lowercase() {
903 return false;
904 }
905 }
906
907 return true;
908 }
909
910 true
911}
912
913/// Some SignatureResult fields need to be auto-populated.
914fn signature_result_auto_populate(
915 signature_result: &mut signatures::common::SignatureResult,
916 signature: &signatures::common::Signature,
917) {
918 signature_result.id = Uuid::new_v4().to_string();
919 signature_result.name = signature.name.clone();
920 signature_result.always_display = signature.always_display;
921}
922

After

1//! Primary Binwalk interface.
2
3use aho_corasick::AhoCorasick;
4use log::{debug, error, info, warn};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path;
9use uuid::Uuid;
10
11#[cfg(windows)]
12use std::os::windows;
13
14#[cfg(unix)]
15use std::os::unix;
16
17use crate::common::{is_offset_safe, read_file};
18use crate::extractors;
19use crate::magic;
20use crate::signatures;
21
22/// Returned on initialization error
23#[derive(Debug, Default, Clone)]
24pub struct BinwalkError {
25 pub message: String,
26}
27
28impl BinwalkError {
29 pub fn new(message: &str) -> Self {
30 BinwalkError {
31 message: message.to_string(),
32 }
33 }
34}
35
36/// Analysis results returned by Binwalk::analyze
37#[derive(Debug, Default, Clone, Serialize, Deserialize)]
38pub struct AnalysisResults {
39 /// Path to the file that was analyzed
40 pub file_path: String,
41 /// File signature results, as returned by Binwalk::scan
42 pub file_map: Vec<signatures::common::SignatureResult>,
43 /// File extraction results, as returned by Binwalk::extract.
44 /// HashMap key is the corresponding SignatureResult.id value in `file_map`.
45 pub extractions: HashMap<String, extractors::common::ExtractionResult>,
46}
47
48/// Analyze files / memory for file signatures
49///
50/// ## Example
51///
52/// ```
53/// use binwalk::Binwalk;
54///
55/// let target_file = "/bin/ls";
56/// let data_to_scan = std::fs::read(target_file).expect("Unable to read file");
57///
58/// let binwalker = Binwalk::new();
59///
60/// let signature_results = binwalker.scan(&data_to_scan);
61///
62/// for result in &signature_results {
63/// println!("Found '{}' at offset {:#X}", result.description, result.offset);
64/// }
65/// ```
66#[derive(Debug, Default, Clone)]
67pub struct Binwalk {
68 /// Count of all signatures (short and regular)
69 pub signature_count: usize,
70 /// Count of all magic patterns (short and regular)
71 pub pattern_count: usize,
72 /// The base file requested for analysis
73 pub base_target_file: String,
74 /// The base output directory for extracted files
75 pub base_output_directory: String,
76 /// A list of signatures that must start at offset 0
77 pub short_signatures: Vec<signatures::common::Signature>,
78 /// A list of magic bytes to search for throughout the entire file
79 pub patterns: Vec<Vec<u8>>,
80 /// Maps patterns to their corresponding signature
81 pub pattern_signature_table: HashMap<usize, signatures::common::Signature>,
82 /// Maps signatures to their corresponding extractors
83 pub extractor_lookup_table: HashMap<String, Option<extractors::common::Extractor>>,
84}
85
86impl Binwalk {
87 /// Create a new Binwalk instance with all default values.
88 /// Equivalent to `Binwalk::configure(None, None, None, None, None, false)`.
89 ///
90 /// ## Example
91 ///
92 /// ```
93 /// use binwalk::Binwalk;
94 ///
95 /// let binwalker = Binwalk::new();
96 /// ```
97 #[allow(dead_code)]
98 pub fn new() -> Binwalk {
99 Binwalk::configure(None, None, None, None, None, false).unwrap()
100 }
101
102 /// Create a new Binwalk instance.
103 ///
104 /// If `target_file_name` and `output_directory` are specified, the `output_directory` will be created if it does not
105 /// already exist, and a symlink to `target_file_name` will be placed inside the `output_directory`. The path to this
106 /// symlink is placed in `Binwalk.base_target_file`.
107 ///
108 /// The `include` and `exclude` arguments specify include and exclude signature filters. The String values contained
109 /// in these arguments must match the `Signature.name` values defined in magic.rs.
110 ///
111 /// Additional user-defined signatures may be provided via the `signatures` argument.
112 ///
113 /// ## Example
114 ///
115 /// ```
116 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_102_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
117 /// use binwalk::Binwalk;
118 ///
119 /// // Don't scan for these file signatures
120 /// let exclude_filters: Vec<String> = vec!["jpeg".to_string(), "png".to_string()];
121 ///
122 /// let binwalker = Binwalk::configure(None,
123 /// None,
124 /// None,
125 /// Some(exclude_filters),
126 /// None,
127 /// false)?;
128 /// # Ok(binwalker)
129 /// # } _doctest_main_src_binwalk_rs_102_0(); }
130 /// ```
131 pub fn configure(
132 target_file_name: Option<String>,
133 output_directory: Option<String>,
134 include: Option<Vec<String>>,
135 exclude: Option<Vec<String>>,
136 signatures: Option<Vec<signatures::common::Signature>>,
137 full_search: bool,
138 ) -> Result<Binwalk, BinwalkError> {
139 let mut new_instance = Binwalk {
140 ..Default::default()
141 };
142
143 // Target file is optional, especially if being called via the library
144 if let Some(target_file) = target_file_name {
145 // Set the target file path, make it an absolute path
146 match path::absolute(&target_file) {
147 Err(_) => {
148 return Err(BinwalkError::new(&format!(
149 "Failed to get absolute path for '{target_file}'"
150 )));
151 }
152 Ok(abspath) => {
153 new_instance.base_target_file = abspath.display().to_string();
154 }
155 }
156
157 // If an output extraction directory was also specified, initialize it
158 if let Some(extraction_directory) = output_directory {
159 // Make the extraction directory an absolute path
160 match path::absolute(&extraction_directory) {
161 Err(_) => {
162 return Err(BinwalkError::new(&format!(
163 "Failed to get absolute path for '{extraction_directory}'"
164 )));
165 }
166 Ok(abspath) => {
167 new_instance.base_output_directory = abspath.display().to_string();
168 }
169 }
170
171 // Initialize the extraction directory. This will create the directory if it
172 // does not exist, and create a symlink inside the directory that points to
173 // the specified target file.
174 match init_extraction_directory(
175 &new_instance.base_target_file,
176 &new_instance.base_output_directory,
177 ) {
178 Err(e) => {
179 return Err(BinwalkError::new(&format!(
180 "Failed to initialize extraction directory: {e}"
181 )));
182 }
183 Ok(new_target_file_path) => {
184 // This is the new base target path (a symlink inside the extraction directory)
185 new_instance.base_target_file = new_target_file_path.clone();
186 }
187 }
188 }
189 }
190
191 // Load all internal signature patterns
192 let mut signature_patterns = magic::patterns();
193
194 // Include any user-defined signature patterns
195 if let Some(user_defined_signature_patterns) = signatures {
196 signature_patterns.extend(user_defined_signature_patterns);
197 }
198
199 // Load magic signatures
200 for signature in signature_patterns.clone() {
201 // Check if this signature should be included
202 if !include_signature(&signature, &include, &exclude) {
203 continue;
204 }
205
206 // Keep a count of total unique signatures that are supported
207 new_instance.signature_count += 1;
208
209 // Keep a count of the total number of magic patterns
210 new_instance.pattern_count += signature.magic.len();
211
212 // Create a lookup table which associates each signature to its respective extractor
213 new_instance
214 .extractor_lookup_table
215 .insert(signature.name.clone(), signature.extractor.clone());
216
217 // Each signature may have multiple magic bytes associated with it
218 for pattern in signature.magic.clone() {
219 if signature.short && !full_search {
220 // These are short patterns, and should only be searched for at the very beginning of a file
221 new_instance.short_signatures.push(signature.clone());
222 break;
223 } else {
224 /*
225 * Need to keep a mapping of the pattern index and its associated signature
226 * so that when a match is found it can be resolved back to the signature from
227 * which it came.
228 */
229 new_instance
230 .pattern_signature_table
231 .insert(new_instance.patterns.len(), signature.clone());
232
233 // Add these magic bytes to the list of patterns
234 new_instance.patterns.push(pattern.to_vec());
235 }
236 }
237 }
238
239 Ok(new_instance)
240 }
241
242 /// Scan a file for magic signatures.
243 /// Returns a list of validated magic signatures representing the known contents of the file.
244 ///
245 /// ## Example
246 ///
247 /// ```
248 /// use binwalk::Binwalk;
249 ///
250 /// let target_file = "/bin/ls";
251 /// let data_to_scan = std::fs::read(target_file).expect("Unable to read file");
252 ///
253 /// let binwalker = Binwalk::new();
254 ///
255 /// let signature_results = binwalker.scan(&data_to_scan);
256 ///
257 /// for result in &signature_results {
258 /// println!("{:#X} {}", result.offset, result.description);
259 /// }
260 ///
261 /// assert!(signature_results.len() > 0);
262 /// ```
263 pub fn scan(&self, file_data: &[u8]) -> Vec<signatures::common::SignatureResult> {
264 const FILE_START_OFFSET: usize = 0;
265
266 let mut index_adjustment: usize = 0;
267 let mut next_valid_offset: usize = 0;
268 let mut previous_valid_offset = None;
269
270 let available_data = file_data.len();
271
272 // A list of identified signatures, representing a "map" of the file data
273 let mut file_map: Vec<signatures::common::SignatureResult> = vec![];
274
275 /*
276 * Check beginning of file for short signatures.
277 * These signatures are only valid if they occur at the very beginning of a file.
278 * This is typically because the signatures are very short and they are likely
279 * to occur randomly throughout the file, so this prevents having to validate many
280 * false positve matches.
281 */
282 for signature in &self.short_signatures {
283 for magic in signature.magic.clone() {
284 let magic_start = FILE_START_OFFSET + signature.magic_offset;
285 let magic_end = magic_start + magic.len();
286
287 if file_data.len() > magic_end && file_data[magic_start..magic_end] == magic {
288 debug!(
289 "Found {} short magic match at offset {:#X}",
290 signature.description, magic_start
291 );
292
293 if let Ok(mut signature_result) = (signature.parser)(file_data, magic_start) {
294 // Auto populate some signature result fields
295 signature_result_auto_populate(&mut signature_result, signature);
296
297 // Add this signature to the file map
298 file_map.push(signature_result.clone());
299 info!(
300 "Found valid {} short signature at offset {:#X}",
301 signature_result.name, FILE_START_OFFSET
302 );
303
304 // Only update the next_valid_offset if confidence is high; these are, after all, short signatures
305 if signature_result.confidence >= signatures::common::CONFIDENCE_HIGH {
306 next_valid_offset = signature_result.offset + signature_result.size;
307 }
308
309 // Only one signature can match at fixed offset 0
310 break;
311 } else {
312 debug!(
313 "{} short signature match at offset {:#X} is invalid",
314 signature.description, FILE_START_OFFSET
315 );
316 }
317 }
318 }
319 }
320
321 /*
322 * Same pattern matching algorithm used by fgrep.
323 * This will search for all magic byte patterns in the file data, all at once.
324 * https://en.wikipedia.org/wiki/Aho–Corasick_algorithm
325 */
326 let grep = AhoCorasick::new(self.patterns.clone()).unwrap();
327
328 debug!("Running Aho-Corasick scan");
329
330 /*
331 * Outer loop wrapper for AhoCorasick scan loop. This will loop until:
332 *
333 * 1) next_valid_offset exceeds available_data
334 * 2) previous_valid_offset <= next_valid_offset
335 */
336 while is_offset_safe(available_data, next_valid_offset, previous_valid_offset) {
337 // Update the previous valid offset in praparation for the next loop iteration
338 previous_valid_offset = Some(next_valid_offset);
339
340 debug!("Continuing scan from offset {next_valid_offset:#X}");
341
342 /*
343 * Run a new AhoCorasick scan starting at the next valid offset in the file data.
344 * This will loop until:
345 *
346 * 1) All data has been exhausted, in which case previous_valid_offset and next_valid_offset
347 * will be identical, causing the outer while loop to break.
348 * 2) A valid signature with a defined size is found, in which case next_valid_offset will
349 * be updated to point the end of the valid signature data, causing a new AhoCorasick
350 * scan to start at the new next_valid_offset file location.
351 */
352 for magic_match in grep.find_overlapping_iter(&file_data[next_valid_offset..]) {
353 // Get the location of the magic bytes inside the file data
354 let magic_offset: usize = next_valid_offset + magic_match.start();
355
356 // Get the signature associated with this magic signature
357 let magic_pattern_index: usize = magic_match.pattern().as_usize();
358 let signature: signatures::common::Signature = self
359 .pattern_signature_table
360 .get(&magic_pattern_index)
361 .unwrap()
362 .clone();
363
364 debug!(
365 "Found {} magic match at offset {:#X}",
366 signature.description, magic_offset
367 );
368
369 /*
370 * Invoke the signature parser to parse and validate the signature.
371 * An error indicates a false positive match for the signature type.
372 */
373 if let Ok(mut signature_result) = (signature.parser)(file_data, magic_offset) {
374 // Calculate the end of this signature's data
375 let signature_end_offset = signature_result.offset + signature_result.size;
376
377 // Sanity check the reported offset and size vs file size
378 if signature_end_offset > available_data {
379 info!("Signature {} extends beyond EOF; ignoring", signature.name);
380 // Continue inner loop
381 continue;
382 }
383
384 // Auto populate some signature result fields
385 signature_result_auto_populate(&mut signature_result, &signature);
386
387 // Add this signature to the file map
388 file_map.push(signature_result.clone());
389
390 info!(
391 "Found valid {} signature at offset {:#X}",
392 signature_result.name, signature_result.offset
393 );
394
395 // Only update the next_valid_offset if confidence is at least medium
396 if signature_result.confidence >= signatures::common::CONFIDENCE_MEDIUM {
397 // Only update the next_valid offset if the end of the signature reported the size of its contents
398 if signature_result.size > 0 {
399 // This file's signature has a known size, so there's no need to scan inside this file's data.
400 // Update next_valid_offset to point to the end of this file signature and break out of the
401 // inner loop.
402 next_valid_offset = signature_end_offset;
403 break;
404 }
405 }
406 } else {
407 debug!(
408 "{} magic match at offset {:#X} is invalid",
409 signature.description, magic_offset
410 );
411 }
412 }
413 }
414
415 debug!("Aho-Corasick scan found {} magic matches", file_map.len());
416
417 /*
418 * A file's magic bytes do not always start at the beginning of a file, meaning that it is possible
419 * that the order in which the signatures were found in the file data is not the order in which we
420 * want to process/validate the signatures. Each signature's parser function will report the correct
421 * starting offset for the signature, so sort the file_map by the SignatureResult.offset value.
422 */
423 file_map.sort();
424 next_valid_offset = 0;
425
426 /*
427 * Now that signatures are in the correct order, identify and any overlapping signatures
428 * (such as gzip files identified within a tarball archive), signatures with the same reported offset,
429 * and any signatures with an invalid reported size (i.e., the size extends beyond the end of available file_data).
430 */
431 for mut i in 0..file_map.len() {
432 // Some entries may have been removed from the file_map list in previous loop iterations; adjust the index accordingly
433 i -= index_adjustment;
434
435 // Make sure the file map index is valid
436 if file_map.is_empty() || i >= file_map.len() {
437 break;
438 }
439
440 let this_signature = file_map[i].clone();
441 let remaining_available_size = file_data.len() - this_signature.offset;
442
443 // Check if the previous file map entry had the same reported starting offset as this one
444 if i > 0 && this_signature.offset == file_map[i - 1].offset {
445 // Get the previous signature in the file map
446 let previous_signature = file_map[i - 1].clone();
447
448 // If this file map entry and the conflicting entry do not have the same confidence level, default to the one with highest confidence
449 if this_signature.confidence != previous_signature.confidence {
450 debug!(
451 "Conflicting signatures at offset {:#X}; defaulting to the signature with highest confidence",
452 this_signature.offset
453 );
454
455 // If this signature is higher confidence, invalidate the previous signature
456 if this_signature.confidence > previous_signature.confidence {
457 file_map.remove(i - 1);
458 index_adjustment += 1;
459
460 // Else, this signature has a lower confidence; invalidate this signature and continue to the next signature in the list
461 } else {
462 file_map.remove(i);
463 index_adjustment += 1;
464 continue;
465 }
466
467 // Conflicting signatures have identical confidence levels; defer to the previously vetted signature
468 } else {
469 debug!(
470 "Conflicting signatures at offset {:#X} with the same confidence; first come, first served",
471 this_signature.offset
472 );
473 file_map.remove(i);
474 index_adjustment += 1;
475 continue;
476 }
477
478 // Else, if the offsets don't conflict, make sure this signature doesn't fall inside a previously identified signature's data
479 } else if this_signature.offset < next_valid_offset {
480 debug!(
481 "Signature {} at offset {:#X} contains conflicting data; ignoring",
482 this_signature.name, this_signature.offset
483 );
484 file_map.remove(i);
485 index_adjustment += 1;
486 continue;
487 }
488
489 // If we've made it this far, make sure this signature's data doesn't extend beyond EOF and that the file data doesn't wrap around
490 if this_signature.size > remaining_available_size
491 || ((this_signature.offset + this_signature.size) as isize) < 0
492 {
493 debug!(
494 "Signature {} at offset {:#X} claims its size extends beyond EOF; ignoring",
495 this_signature.name, this_signature.offset
496 );
497 file_map.remove(i);
498 index_adjustment += 1;
499 continue;
500 }
501
502 // This signature looks OK, update the next_valid_offset to be the end of this signature's data, only if we're fairly confident in the signature
503 if this_signature.confidence >= signatures::common::CONFIDENCE_MEDIUM {
504 next_valid_offset = this_signature.offset + this_signature.size;
505 }
506 }
507
508 /*
509 * Ideally, all signatures would report their size; some file formats do not specify a size, and the only
510 * way to determine the size is to extract the file format (compressed data, for example).
511 * For signatures with a reported size of 0, update their size to be the start of the next signature, or EOF.
512 * This makes the assumption that there are no false positives or false negatives.
513 *
514 * False negatives (i.e., there is some other file format or data between this signature and the next that
515 * was not correctly identified) is less problematic, as this will overestimate the size of this signature,
516 * but most extraction utilities don't care about this extra trailing data being included.
517 *
518 * False positives (i.e., some data inside of this signature is identified as some other file type) can cause
519 * this signature's file data to become truncated, which will inevitably result in a failed, or partial, extraction.
520 *
521 * Thus, signatures must be very good at validating magic matches and eliminating false positives.
522 */
523 for i in 0..file_map.len() {
524 if file_map[i].size == 0 {
525 // Index of the next file map entry, if any
526 let next_index = i + 1;
527
528 // By default, assume this signature goes to EOF
529 let mut next_offset: usize = file_data.len();
530
531 // If there are more entries in the file map
532 if next_index < file_map.len() {
533 // Look through all remaining file map entries for one with medium to high confidence
534 for file_map_entry in file_map.iter().skip(next_index) {
535 if file_map_entry.confidence >= signatures::common::CONFIDENCE_MEDIUM {
536 // If a signature of at least medium confidence is found, assume that *this* signature ends there
537 next_offset = file_map_entry.offset;
538 break;
539 }
540 }
541 }
542
543 file_map[i].size = next_offset - file_map[i].offset;
544 warn!(
545 "Signature {}:{:#X} size is unknown; assuming size of {:#X} bytes",
546 file_map[i].name, file_map[i].offset, file_map[i].size
547 );
548 } else {
549 debug!(
550 "Signature {}:{:#X} has a reported size of {:#X} bytes",
551 file_map[i].name, file_map[i].offset, file_map[i].size
552 );
553 }
554 }
555
556 debug!("Found {} valid signatures", file_map.len());
557
558 file_map
559 }
560
561 /// Extract all extractable signatures found in a file.
562 ///
563 /// ## Example
564 ///
565 /// ```
566 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_529_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
567 /// use binwalk::Binwalk;
568 ///
569 /// let target_path = std::path::Path::new("tests")
570 /// .join("inputs")
571 /// .join("gzip.bin")
572 /// .display()
573 /// .to_string();
574 ///
575 /// let extraction_directory = std::path::Path::new("tests")
576 /// .join("extractions")
577 /// .display()
578 /// .to_string();
579 ///
580 /// # std::fs::remove_dir_all(&extraction_directory);
581 /// let binwalker = Binwalk::configure(Some(target_path),
582 /// Some(extraction_directory.clone()),
583 /// None,
584 /// None,
585 /// None,
586 /// false)?;
587 ///
588 /// let file_data = std::fs::read(&binwalker.base_target_file).expect("Unable to read file");
589 ///
590 /// let scan_results = binwalker.scan(&file_data);
591 /// let extraction_results = binwalker.extract(&file_data, &binwalker.base_target_file, &scan_results);
592 ///
593 /// assert_eq!(scan_results.len(), 1);
594 /// assert_eq!(extraction_results.len(), 1);
595 /// assert_eq!(std::path::Path::new(&extraction_directory)
596 /// .join("gzip.bin.extracted")
597 /// .join("0")
598 /// .join("decompressed.bin")
599 /// .exists(), true);
600 /// # std::fs::remove_dir_all(&extraction_directory);
601 /// # Ok(binwalker)
602 /// # } _doctest_main_src_binwalk_rs_529_0(); }
603 /// ```
604 pub fn extract(
605 &self,
606 file_data: &[u8],
607 file_name: impl Into<String>,
608 file_map: &Vec<signatures::common::SignatureResult>,
609 ) -> HashMap<String, extractors::common::ExtractionResult> {
610 let file_path = file_name.into();
611 let mut extraction_results: HashMap<String, extractors::common::ExtractionResult> =
612 HashMap::new();
613
614 // Spawn extractors for each extractable signature
615 for signature in file_map {
616 // Signatures may opt to not perform extraction; honor this request
617 if signature.extraction_declined {
618 continue;
619 }
620
621 // Get the extractor for this signature
622 let extractor = self.extractor_lookup_table[&signature.name].clone();
623
624 match &extractor {
625 None => continue,
626 Some(_) => {
627 // Run an extraction for this signature
628 let mut extraction_result =
629 extractors::common::execute(file_data, &file_path, signature, &extractor);
630
631 if !extraction_result.success {
632 debug!(
633 "Extraction failed for {} (ID: {}) {:#X} - {:#X}",
634 signature.name, signature.id, signature.offset, signature.size
635 );
636
637 // Calculate all available data from the start of this signature to EOF
638 let available_data = file_data.len() - signature.offset;
639
640 /*
641 * If extraction failed, it could be due to truncated data (signature matching is not perfect ya know!)
642 * In that case, make one more attempt, this time provide the extractor all the data possible.
643 */
644 if signature.size < available_data {
645 // Create a duplicate signature, but set its reported size to the length of all available data
646 let mut new_signature = signature.clone();
647 new_signature.size = available_data;
648
649 debug!(
650 "Trying extraction for {} (ID: {}) again, this time from {:#X} - {:#X}",
651 new_signature.name,
652 new_signature.id,
653 new_signature.offset,
654 new_signature.size
655 );
656
657 // Re-run the extraction
658 extraction_result = extractors::common::execute(
659 file_data,
660 &file_path,
661 &new_signature,
662 &extractor,
663 );
664 }
665 }
666
667 // Update the HashMap with the result of this extraction attempt
668 extraction_results.insert(signature.id.clone(), extraction_result);
669 }
670 }
671 }
672
673 extraction_results
674 }
675
676 /// Analyze a data buffer and optionally extract the file contents.
677 ///
678 /// ## Example
679 ///
680 /// ```
681 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
682 /// use binwalk::{Binwalk, common};
683 ///
684 /// let target_path = std::path::Path::new("tests")
685 /// .join("inputs")
686 /// .join("gzip.bin")
687 /// .display()
688 /// .to_string();
689 ///
690 /// let extraction_directory = std::path::Path::new("tests")
691 /// .join("extractions")
692 /// .display()
693 /// .to_string();
694 ///
695 /// let file_data = common::read_file(&target_path).expect("Failed to read file data");
696 ///
697 /// # std::fs::remove_dir_all(&extraction_directory);
698 /// let binwalker = Binwalk::configure(Some(target_path),
699 /// Some(extraction_directory.clone()),
700 /// None,
701 /// None,
702 /// None,
703 /// false)?;
704 ///
705 /// let analysis_results = binwalker.analyze_buf(&file_data, &binwalker.base_target_file, true);
706 ///
707 /// assert_eq!(analysis_results.file_map.len(), 1);
708 /// assert_eq!(analysis_results.extractions.len(), 1);
709 /// assert_eq!(std::path::Path::new(&extraction_directory)
710 /// .join("gzip.bin.extracted")
711 /// .join("0")
712 /// .join("decompressed.bin")
713 /// .exists(), true);
714 /// # std::fs::remove_dir_all(&extraction_directory);
715 /// # Ok(binwalker)
716 /// # } _doctest_main_src_binwalk_rs_672_0(); }
717 /// ```
718 pub fn analyze_buf(
719 &self,
720 file_data: &[u8],
721 target_file: impl Into<String>,
722 do_extraction: bool,
723 ) -> AnalysisResults {
724 let file_path = target_file.into();
725
726 // Return value
727 let mut results: AnalysisResults = AnalysisResults {
728 file_path: file_path.clone(),
729 ..Default::default()
730 };
731
732 // Scan file data for signatures
733 debug!("Analysis start: {file_path}");
734 results.file_map = self.scan(file_data);
735
736 // Only extract if told to, and if there were some signatures found in this file
737 if do_extraction && !results.file_map.is_empty() {
738 // Extract everything we can
739 debug!(
740 "Submitting {} signature results to extractor",
741 results.file_map.len()
742 );
743 results.extractions = self.extract(file_data, &file_path, &results.file_map);
744 }
745
746 debug!("Analysis end: {file_path}");
747
748 results
749 }
750
751 /// Analyze a file on disk and optionally extract its contents.
752 ///
753 /// ## Example
754 ///
755 /// ```
756 /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_745_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
757 /// use binwalk::Binwalk;
758 ///
759 /// let target_path = std::path::Path::new("tests")
760 /// .join("inputs")
761 /// .join("gzip.bin")
762 /// .display()
763 /// .to_string();
764 ///
765 /// let extraction_directory = std::path::Path::new("tests")
766 /// .join("extractions")
767 /// .display()
768 /// .to_string();
769 ///
770 /// # std::fs::remove_dir_all(&extraction_directory);
771 /// let binwalker = Binwalk::configure(Some(target_path),
772 /// Some(extraction_directory.clone()),
773 /// None,
774 /// None,
775 /// None,
776 /// false)?;
777 ///
778 /// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true);
779 ///
780 /// assert_eq!(analysis_results.file_map.len(), 1);
781 /// assert_eq!(analysis_results.extractions.len(), 1);
782 /// assert_eq!(std::path::Path::new(&extraction_directory)
783 /// .join("gzip.bin.extracted")
784 /// .join("0")
785 /// .join("decompressed.bin")
786 /// .exists(), true);
787 /// # std::fs::remove_dir_all(&extraction_directory);
788 /// # Ok(binwalker)
789 /// # } _doctest_main_src_binwalk_rs_745_0(); }
790 /// ```
791 #[allow(dead_code)]
792 pub fn analyze(&self, target_file: impl Into<String>, do_extraction: bool) -> AnalysisResults {
793 let file_path = target_file.into();
794
795 let file_data = match read_file(&file_path) {
796 Err(_) => {
797 error!("Failed to read data from {file_path}");
798 b"".to_vec()
799 }
800 Ok(data) => data,
801 };
802
803 self.analyze_buf(&file_data, &file_path, do_extraction)
804 }
805}
806
807/// Initializes the extraction output directory
808fn init_extraction_directory(
809 target_file: &str,
810 extraction_directory: &str,
811) -> Result<String, std::io::Error> {
812 // Create the output directory, equivalent of mkdir -p
813 match fs::create_dir_all(extraction_directory) {
814 Ok(_) => {
815 debug!("Created base output directory: '{extraction_directory}'");
816 }
817 Err(e) => {
818 error!("Failed to create base output directory '{extraction_directory}': {e}");
819 return Err(e);
820 }
821 }
822
823 // Create a Path for the target file
824 let target_path = path::Path::new(&target_file);
825
826 // Build a symlink path to the target file in the extraction directory
827 let link_target_path_str = format!(
828 "{}{}{}",
829 extraction_directory,
830 path::MAIN_SEPARATOR,
831 target_path.file_name().unwrap().to_str().unwrap()
832 );
833
834 // Create a path for the symlink target path
835 let link_path = path::Path::new(&link_target_path_str);
836
837 if link_path.exists() {
838 return Ok(link_target_path_str);
839 }
840
841 debug!(
842 "Creating symlink from {} -> {}",
843 link_path.display(),
844 target_path.display()
845 );
846
847 // Create a symlink from inside the extraction directory to the specified target file
848 #[cfg(unix)]
849 {
850 match unix::fs::symlink(target_path, link_path) {
851 Ok(_) => Ok(link_target_path_str),
852 Err(e) => {
853 error!(
854 "Failed to create symlink {} -> {}: {}",
855 link_path.display(),
856 target_path.display(),
857 e
858 );
859 Err(e)
860 }
861 }
862 }
863 #[cfg(windows)]
864 {
865 match std::fs::hard_link(target_path, link_path) {
866 Ok(_) => {
867 return Ok(link_target_path_str);
868 }
869 Err(e) => {
870 error!(
871 "Failed to create hardlink {} -> {}: {}",
872 link_path.display(),
873 target_path.display(),
874 e
875 );
876 return Err(e);
877 }
878 }
879 }
880}
881
882/// Returns true if the signature should be included for file analysis, else returns false.
883fn include_signature(
884 signature: &signatures::common::Signature,
885 include: &Option<Vec<String>>,
886 exclude: &Option<Vec<String>>,
887) -> bool {
888 if let Some(include_signatures) = include {
889 for include_str in include_signatures {
890 if signature.name.to_lowercase() == include_str.to_lowercase() {
891 return true;
892 }
893 }
894
895 return false;
896 }
897
898 if let Some(exclude_signatures) = exclude {
899 for exclude_str in exclude_signatures {
900 if signature.name.to_lowercase() == exclude_str.to_lowercase() {
901 return false;
902 }
903 }
904
905 return true;
906 }
907
908 true
909}
910
911/// Some SignatureResult fields need to be auto-populated.
912fn signature_result_auto_populate(
913 signature_result: &mut signatures::common::SignatureResult,
914 signature: &signatures::common::Signature,
915) {
916 signature_result.id = Uuid::new_v4().to_string();
917 signature_result.name = signature.name.clone();
918 signature_result.always_display = signature.always_display;
919}
920

Before

source_file
line_comment
// "//"
inner_doc_comment_marker
! "!"
doc_comment " Primary Binwalk interface.\n"
use_declaration
use "use"
scoped_identifier
identifier "aho_corasick"
:: "::"
identifier "AhoCorasick"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "log"
:: "::"
use_list
{ "{"
identifier "debug"
, ","
identifier "error"
, ","
identifier "info"
, ","
identifier "warn"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "serde"
:: "::"
use_list
{ "{"
identifier "Deserialize"
, ","
identifier "Serialize"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "collections"
:: "::"
identifier "HashMap"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "fs"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "path"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "uuid"
:: "::"
identifier "Uuid"
; ";"
attribute_item
# "#"
[ "["
attribute
identifier "cfg"
token_tree
( "("
identifier "windows"
) ")"
] "]"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "os"
:: "::"
identifier "windows"
; ";"
attribute_item
# "#"
[ "["
attribute
identifier "cfg"
token_tree
( "("
identifier "unix"
) ")"
] "]"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "os"
:: "::"
identifier "unix"
; ";"
use_declaration
use "use"
scoped_use_list
scoped_identifier
crate "crate"
:: "::"
identifier "common"
:: "::"
use_list
{ "{"
identifier "is_offset_safe"
, ","
identifier "read_file"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "extractors"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "magic"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "signatures"
; ";"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Returned on initialization error\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
struct_item
visibility_modifier
pub "pub"
struct "struct"
type_identifier "BinwalkError"
field_declaration_list
{ "{"
field_declaration
visibility_modifier
pub "pub"
field_identifier "message"
: ":"
type_identifier "String"
, ","
} "}"
impl_item (+42 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Analysis results returned by Binwalk::analyze\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
, ","
identifier "Serialize"
, ","
identifier "Deserialize"
) ")"
] "]"
struct_item (+73 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Analyze files / memory for file signatures\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ## Example\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ```\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " use binwalk::Binwalk;\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let target_file = \"/bin/ls\";\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let data_to_scan = std::fs::read(target_file).expect(\"Unabl"...
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let binwalker = Binwalk::new();\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let signature_results = binwalker.scan(&data_to_scan);\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " for result in &signature_results {\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " println!(\"Found '{}' at offset {:#X}\", result.descripti"...
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " }\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ```\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
struct_item (+156 nodes collapsed)
impl_item (+4054 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Initializes the extraction output directory\n"
function_item (+448 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Returns true if the signature should be included for file a"...
function_item (+186 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Some SignatureResult fields need to be auto-populated.\n"
function_item (+90 nodes collapsed)

After

source_file
line_comment
// "//"
inner_doc_comment_marker
! "!"
doc_comment " Primary Binwalk interface.\n"
use_declaration
use "use"
scoped_identifier
identifier "aho_corasick"
:: "::"
identifier "AhoCorasick"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "log"
:: "::"
use_list
{ "{"
identifier "debug"
, ","
identifier "error"
, ","
identifier "info"
, ","
identifier "warn"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "serde"
:: "::"
use_list
{ "{"
identifier "Deserialize"
, ","
identifier "Serialize"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "collections"
:: "::"
identifier "HashMap"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "fs"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "path"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "uuid"
:: "::"
identifier "Uuid"
; ";"
attribute_item
# "#"
[ "["
attribute
identifier "cfg"
token_tree
( "("
identifier "windows"
) ")"
] "]"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "os"
:: "::"
identifier "windows"
; ";"
attribute_item
# "#"
[ "["
attribute
identifier "cfg"
token_tree
( "("
identifier "unix"
) ")"
] "]"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "os"
:: "::"
identifier "unix"
; ";"
use_declaration
use "use"
scoped_use_list
scoped_identifier
crate "crate"
:: "::"
identifier "common"
:: "::"
use_list
{ "{"
identifier "is_offset_safe"
, ","
identifier "read_file"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "extractors"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "magic"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "signatures"
; ";"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Returned on initialization error\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
struct_item
visibility_modifier
pub "pub"
struct "struct"
type_identifier "BinwalkError"
field_declaration_list
{ "{"
field_declaration
visibility_modifier
pub "pub"
field_identifier "message"
: ":"
type_identifier "String"
, ","
} "}"
impl_item (+42 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Analysis results returned by Binwalk::analyze\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
, ","
identifier "Serialize"
, ","
identifier "Deserialize"
) ")"
] "]"
struct_item (+73 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Analyze files / memory for file signatures\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ## Example\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ```\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " use binwalk::Binwalk;\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let target_file = \"/bin/ls\";\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let data_to_scan = std::fs::read(target_file).expect(\"Unabl"...
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let binwalker = Binwalk::new();\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " let signature_results = binwalker.scan(&data_to_scan);\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment "\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " for result in &signature_results {\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " println!(\"Found '{}' at offset {:#X}\", result.descripti"...
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " }\n"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " ```\n"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Debug"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
struct_item (+156 nodes collapsed)
impl_item (+4054 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Initializes the extraction output directory\n"
function_item (+448 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Returns true if the signature should be included for file a"...
function_item (+186 nodes collapsed)
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Some SignatureResult fields need to be auto-populated.\n"
function_item (+90 nodes collapsed)