Small Rust scripts
1#!/usr/bin/env -S cargo +nightly -Zscript
2
3---
4[package]
5edition = "2024"
6[dependencies]
7argh = "0.1"
8object = "0.38.0"
9---
10
11use std::fs::OpenOptions;
12use std::io::{self, BufReader, Read, Write};
13use std::path::PathBuf;
14
15use argh::FromArgs;
16use object::{File, Object, ObjectSection, SectionKind};
17
18const MIN_STR_LEN: usize = 4;
19
20const ELF_SIG: [u8; 4] = [0x7F, 0x45, 0x4C, 0x46];
21const MACH_O_32_SIG: [u8; 4] = [0xFE, 0xED, 0xFA, 0xCE];
22const MACH_O_64_SIG: [u8; 4] = [0xFE, 0xED, 0xFA, 0xCF];
23const MACH_O_FAT_SIG: [u8; 4] = [0xCA, 0xFE, 0xBA, 0xBE];
24const MACH_O_32_REV_SIG: [u8; 4] = [0xCE, 0xFA, 0xED, 0xFE];
25const MACH_O_64_REV_SIG: [u8; 4] = [0xCF, 0xFA, 0xED, 0xFE];
26
27#[derive(FromArgs)]
28#[argh(description = "Rust clone of strings(1).")]
29struct App {
30 #[argh(
31 switch,
32 description = "ignore heuristics and read all sections of an object file."
33 )]
34 all: bool,
35
36 #[argh(
37 option,
38 description = "the minimum length a valid string needs to be in order to be printed.",
39 default = "MIN_STR_LEN"
40 )]
41 min_length: usize,
42
43 #[argh(positional, description = "input file.")]
44 file: PathBuf,
45}
46
47fn main() {
48 let app: App = argh::from_env();
49
50 let file = OpenOptions::new()
51 .read(true)
52 .open(app.file)
53 .expect("could not open source");
54
55 let mut reader = BufReader::new(file);
56
57 // Preallocate 4MB of space since we're usually dealing with files
58 let mut contents = Vec::with_capacity(1024 * 1024 * 4);
59 reader.read_to_end(&mut contents);
60
61 let stdout = io::stdout();
62 let mut handle = stdout.lock();
63
64 let mut magic = [0, 0, 0, 0];
65 if contents.len() >= 4 {
66 magic.copy_from_slice(&contents[..4]);
67 }
68
69 if is_object_file(&magic) {
70 print_object_strings(&mut handle, &contents, app.all, app.min_length);
71 } else {
72 print_strings(&mut handle, &contents, app.min_length);
73 }
74}
75
76fn is_object_file(magic: &[u8; 4]) -> bool {
77 magic == &ELF_SIG
78 || magic == &MACH_O_FAT_SIG
79 || magic == &MACH_O_32_SIG
80 || magic == &MACH_O_64_SIG
81 || magic == &MACH_O_32_REV_SIG
82 || magic == &MACH_O_64_REV_SIG
83}
84
85fn print_object_strings(handle: &mut impl Write, contents: &[u8], all: bool, min_length: usize) {
86 // We could also read this in a lazy fashion but... I'm lazy at the moment ;)
87 let object = File::parse(contents).expect("Could not parse object file");
88 object
89 .sections()
90 .filter(|section| {
91 all || matches!(
92 section.kind(),
93 SectionKind::Data | SectionKind::ReadOnlyString | SectionKind::ReadOnlyData
94 )
95 })
96 .for_each(|section| {
97 let contents = section.data().expect("section should have data");
98 print_strings(handle, contents, min_length);
99 });
100}
101
102fn print_strings(handle: &mut impl Write, contents: &[u8], min_length: usize) {
103 let mut start = 0;
104 let mut end = 0;
105
106 for c in contents.iter() {
107 let is_printable = c.is_ascii_graphic() || c.is_ascii_whitespace();
108 let has_minimum_len = (end - start) >= min_length;
109
110 if !is_printable && has_minimum_len {
111 handle.write(&contents[start..end]);
112 handle.write(&[b'\n']);
113 start = end + 1;
114 } else if !is_printable {
115 start = end + 1;
116 }
117
118 end += 1;
119 }
120
121 handle.flush();
122}