Skip to main content

binsec/check/
mach.rs

1//! ### Mach-O Specific Compilation Checks:
2//!
3//! * TODO
4//!
5//! ### Exploit Mitigations:
6//!
7//! * NX (Non-eXecutable bit) stack
8//! * NX (Non-eXecutable bit) heap
9//! * Position-Independent Executable
10//! * Stack Canaries
11//! * Restricted segment
12//! * __PAGEZERO segment
13use goblin::mach::{header, MachO};
14use serde_json::json;
15
16use crate::check::{Analyze, GenericMap};
17use crate::BinResult;
18
19use super::UniversalCompilationProperties;
20
21const MH_PIE: u32 = 0x200000;
22const MH_ALLOW_STACK_EXECUTION: u32 = 0x20000;
23const MH_NO_HEAP_EXECUTION: u32 = 0x1000000;
24
25impl UniversalCompilationProperties for MachO<'_> {
26    fn binary_type(&self) -> &str {
27        header::filetype_to_str(self.header.filetype)
28    }
29
30    fn is_stripped(&self) -> bool {
31        self.symbols.is_none()
32    }
33
34    // TODO: Mach-O compiler-runtime detection is not implemented yet.
35    fn compiler_runtime(&self, _bytes: &[u8]) -> Option<String> {
36        None
37    }
38}
39
40trait MachOMitigations {
41    fn executable_stack(&self) -> bool;
42    fn executable_heap(&self) -> bool;
43    fn position_independent(&self) -> bool;
44    fn stack_canary(&self) -> bool;
45    fn restricted_segment(&self) -> bool;
46    fn pagezero_segment(&self) -> bool;
47}
48
49impl MachOMitigations for MachO<'_> {
50    fn executable_stack(&self) -> bool {
51        matches!(self.header.flags & MH_ALLOW_STACK_EXECUTION, 0)
52    }
53
54    fn executable_heap(&self) -> bool {
55        matches!(self.header.flags & MH_NO_HEAP_EXECUTION, 0)
56    }
57
58    fn position_independent(&self) -> bool {
59        matches!(self.header.flags & MH_PIE, 0)
60    }
61
62    // Check for stack canary by finding canary functions in imports
63    fn stack_canary(&self) -> bool {
64        match self.imports() {
65            Ok(imports) => imports
66                .iter()
67                .any(|x| x.name == "__stack_chk_fail" || x.name == "__stack_chk_guard"),
68            Err(_) => false,
69        }
70    }
71
72    // Check for __RESTRICT section for stopping dylib injection
73    fn restricted_segment(&self) -> bool {
74        self.segments
75            .iter()
76            .filter_map(|s| s.name().ok())
77            .any(|s| s.to_lowercase() == "__restrict")
78    }
79
80    fn pagezero_segment(&self) -> bool {
81        self.segments
82            .iter()
83            .filter_map(|s| s.name().ok())
84            .any(|s| s.to_lowercase() == "__PAGEZERO")
85    }
86}
87
88impl Analyze for MachO<'_> {
89    fn compilation(&self, _bytes: &[u8]) -> BinResult<GenericMap> {
90        let mut comp_map = GenericMap::new();
91        comp_map.insert("Binary Type".to_string(), json!(self.binary_type()));
92        comp_map.insert("Debug Stripped".to_string(), json!(self.is_stripped()));
93        Ok(comp_map)
94    }
95
96    fn mitigations(&self) -> GenericMap {
97        let mut mitigate_map: GenericMap = GenericMap::new();
98        mitigate_map.insert(
99            "Non-executable Stack".to_string(),
100            json!(self.executable_stack()),
101        );
102        mitigate_map.insert(
103            "Non-executable Heap".to_string(),
104            json!(self.executable_heap()),
105        );
106        mitigate_map.insert(
107            "Position Independent Executable / ASLR".to_string(),
108            json!(self.position_independent()),
109        );
110        mitigate_map.insert("Stack Canary".to_string(), json!(self.stack_canary()));
111        mitigate_map.insert(
112            "__RESTRICT segment".to_string(),
113            json!(self.restricted_segment()),
114        );
115        mitigate_map.insert(
116            "__PAGEZERO segment".to_string(),
117            json!(self.pagezero_segment()),
118        );
119
120        mitigate_map
121    }
122
123    fn instrumentation(&self) -> GenericMap {
124        let mut instr_map: GenericMap = GenericMap::new();
125        let asan: bool = match self.imports() {
126            Ok(imports) => imports.iter().any(|x| x.name.starts_with("__asan")),
127            Err(_) => false,
128        };
129
130        if asan {
131            instr_map.insert("Address Sanitizer (ASAN)".to_string(), json!(true));
132        }
133
134        instr_map
135    }
136}