1use 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 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 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 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}