Skip to main content

verifyos_cli/parsers/
macho_scanner.rs

1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, thiserror::Error)]
5pub enum UsageScanError {
6    #[error("Failed to read executable: {0}")]
7    Io(#[from] std::io::Error),
8    #[error("Unable to resolve app executable")]
9    MissingExecutable,
10}
11
12#[derive(Debug, Default)]
13pub struct UsageScan {
14    pub required_keys: HashSet<&'static str>,
15    pub requires_location_key: bool,
16    pub evidence: HashSet<&'static str>,
17}
18
19#[derive(Debug, Default)]
20pub struct CapabilityScan {
21    pub detected: HashSet<&'static str>,
22    pub evidence: HashSet<&'static str>,
23}
24
25pub fn scan_usage_from_app_bundle(app_bundle_path: &Path) -> Result<UsageScan, UsageScanError> {
26    let executable =
27        resolve_executable_path(app_bundle_path).ok_or(UsageScanError::MissingExecutable)?;
28    scan_usage_from_executable(&executable)
29}
30
31#[derive(Debug, Default)]
32pub struct PrivateApiScan {
33    pub hits: Vec<&'static str>,
34}
35
36pub fn scan_private_api_from_app_bundle(
37    app_bundle_path: &Path,
38) -> Result<PrivateApiScan, UsageScanError> {
39    let executable =
40        resolve_executable_path(app_bundle_path).ok_or(UsageScanError::MissingExecutable)?;
41    scan_private_api_from_executable(&executable)
42}
43
44pub fn scan_capabilities_from_app_bundle(
45    app_bundle_path: &Path,
46) -> Result<CapabilityScan, UsageScanError> {
47    let executable =
48        resolve_executable_path(app_bundle_path).ok_or(UsageScanError::MissingExecutable)?;
49    scan_capabilities_from_executable(&executable)
50}
51
52fn resolve_executable_path(app_bundle_path: &Path) -> Option<PathBuf> {
53    let app_name = app_bundle_path
54        .file_name()
55        .and_then(|n| n.to_str())
56        .unwrap_or("")
57        .trim_end_matches(".app");
58
59    if app_name.is_empty() {
60        return None;
61    }
62
63    let executable_path = app_bundle_path.join(app_name);
64    if executable_path.exists() {
65        Some(executable_path)
66    } else {
67        None
68    }
69}
70
71fn scan_usage_from_executable(path: &Path) -> Result<UsageScan, UsageScanError> {
72    let bytes = std::fs::read(path)?;
73    let mut scan = UsageScan::default();
74
75    for (signature, requirement) in SIGNATURES {
76        if contains_subslice(&bytes, signature.as_bytes()) {
77            scan.evidence.insert(*signature);
78            match requirement {
79                Requirement::Key(key) => {
80                    scan.required_keys.insert(*key);
81                }
82                Requirement::AnyLocation => {
83                    scan.requires_location_key = true;
84                }
85            }
86        }
87    }
88
89    Ok(scan)
90}
91
92fn scan_private_api_from_executable(path: &Path) -> Result<PrivateApiScan, UsageScanError> {
93    let bytes = std::fs::read(path)?;
94    let mut scan = PrivateApiScan::default();
95
96    for signature in PRIVATE_API_SIGNATURES {
97        if contains_subslice(&bytes, signature.as_bytes()) {
98            scan.hits.push(*signature);
99        }
100    }
101
102    scan.hits.sort_unstable();
103    scan.hits.dedup();
104
105    Ok(scan)
106}
107
108fn scan_capabilities_from_executable(path: &Path) -> Result<CapabilityScan, UsageScanError> {
109    let bytes = std::fs::read(path)?;
110    let mut scan = CapabilityScan::default();
111
112    for (signature, capability) in CAPABILITY_SIGNATURES {
113        if contains_subslice(&bytes, signature.as_bytes()) {
114            scan.evidence.insert(*signature);
115            scan.detected.insert(*capability);
116        }
117    }
118
119    Ok(scan)
120}
121
122fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
123    haystack
124        .windows(needle.len())
125        .any(|window| window == needle)
126}
127
128#[derive(Clone, Copy)]
129enum Requirement {
130    Key(&'static str),
131    AnyLocation,
132}
133
134const SIGNATURES: &[(&str, Requirement)] = &[
135    (
136        "AVCaptureDevice",
137        Requirement::Key("NSCameraUsageDescription"),
138    ),
139    (
140        "AVAudioSession",
141        Requirement::Key("NSMicrophoneUsageDescription"),
142    ),
143    (
144        "AVAudioRecorder",
145        Requirement::Key("NSMicrophoneUsageDescription"),
146    ),
147    (
148        "PHPhotoLibrary",
149        Requirement::Key("NSPhotoLibraryUsageDescription"),
150    ),
151    (
152        "PHPhotoLibraryAddOnly",
153        Requirement::Key("NSPhotoLibraryAddUsageDescription"),
154    ),
155    ("CLLocationManager", Requirement::AnyLocation),
156    (
157        "CBCentralManager",
158        Requirement::Key("NSBluetoothAlwaysUsageDescription"),
159    ),
160    (
161        "CBPeripheralManager",
162        Requirement::Key("NSBluetoothAlwaysUsageDescription"),
163    ),
164    (
165        "CBPeripheral",
166        Requirement::Key("NSBluetoothPeripheralUsageDescription"),
167    ),
168    ("LAContext", Requirement::Key("NSFaceIDUsageDescription")),
169    (
170        "EKEventStore",
171        Requirement::Key("NSCalendarsUsageDescription"),
172    ),
173    (
174        "EKReminder",
175        Requirement::Key("NSRemindersUsageDescription"),
176    ),
177    (
178        "CNContactStore",
179        Requirement::Key("NSContactsUsageDescription"),
180    ),
181    (
182        "SFSpeechRecognizer",
183        Requirement::Key("NSSpeechRecognitionUsageDescription"),
184    ),
185    (
186        "CMMotionManager",
187        Requirement::Key("NSMotionUsageDescription"),
188    ),
189    ("CMPedometer", Requirement::Key("NSMotionUsageDescription")),
190    (
191        "MPMediaLibrary",
192        Requirement::Key("NSAppleMusicUsageDescription"),
193    ),
194    (
195        "HKHealthStore",
196        Requirement::Key("NSHealthShareUsageDescription"),
197    ),
198];
199
200const PRIVATE_API_SIGNATURES: &[&str] = &[
201    "LSApplicationWorkspace",
202    "LSApplicationProxy",
203    "LSAppWorkspace",
204    "SBApplication",
205    "SpringBoard",
206    "MobileGestalt",
207    "UICallApplication",
208    "UIGetScreenImage",
209    "_MGCopyAnswer",
210];
211
212const CAPABILITY_SIGNATURES: &[(&str, &str)] = &[
213    ("AVCaptureDevice", "camera"),
214    ("CLLocationManager", "location"),
215];