#[cfg(feature = "color")]
use colored::Colorize;
use goblin::mach::load_command::CommandVariant;
use goblin::mach::MachO;
use serde::{Deserialize, Serialize};
use std::fmt;
#[cfg(feature = "color")]
use crate::colorize_bool;
const MH_ALLOW_STACK_EXECUTION: u32 = 0x0002_0000;
const MH_PIE: u32 = 0x0020_0000;
const MH_NO_HEAP_EXECUTION: u32 = 0x0100_0000;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Deserialize, Serialize)]
pub struct CheckSecResults {
pub arc: bool,
pub canary: bool,
pub code_signature: bool,
pub encrypted: bool,
pub fortify: bool,
pub fortified: u32,
pub nx_heap: bool,
pub nx_stack: bool,
pub pie: bool,
pub restrict: bool,
pub rpath: bool,
}
impl CheckSecResults {
#[must_use]
pub fn parse(macho: &MachO) -> Self {
Self {
arc: macho.has_arc(),
canary: macho.has_canary(),
code_signature: macho.has_code_signature(),
encrypted: macho.has_encrypted(),
fortify: macho.has_fortify(),
fortified: macho.has_fortified(),
nx_heap: macho.has_nx_heap(),
nx_stack: macho.has_nx_stack(),
pie: macho.has_pie(),
restrict: macho.has_restrict(),
rpath: macho.has_rpath(),
}
}
}
impl fmt::Display for CheckSecResults {
#[cfg(not(feature = "color"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ARC: {} Canary: {} Code Signature: {} Encryption: {} \
Fortify: {} Fortified {:2} NX Heap: {} \
NX Stack: {} PIE: {} Restrict: {} RPath: {}",
self.arc,
self.canary,
self.code_signature,
self.encrypted,
self.fortify,
self.fortified,
self.nx_heap,
self.nx_stack,
self.pie,
self.restrict,
self.rpath
)
}
#[cfg(feature = "color")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} \
{} {} {} {} {} {}",
"ARC:".bold(),
colorize_bool!(self.arc),
"Canary:".bold(),
colorize_bool!(self.canary),
"Code Signature:".bold(),
colorize_bool!(self.code_signature),
"Encrypted:".bold(),
colorize_bool!(self.encrypted),
"Fortify:".bold(),
colorize_bool!(self.fortify),
"Fortified:".bold(),
self.fortified,
"NX Heap:".bold(),
colorize_bool!(self.nx_heap),
"NX Stack:".bold(),
colorize_bool!(self.nx_stack),
"PIE:".bold(),
colorize_bool!(self.pie),
"Restrict:".bold(),
colorize_bool!(self.restrict),
"RPath:".bold(),
colorize_bool!(self.rpath)
)
}
}
pub trait Properties {
fn has_arc(&self) -> bool;
fn has_canary(&self) -> bool;
fn has_code_signature(&self) -> bool;
fn has_encrypted(&self) -> bool;
fn has_fortify(&self) -> bool;
fn has_fortified(&self) -> u32;
fn has_nx_heap(&self) -> bool;
fn has_nx_stack(&self) -> bool;
fn has_pie(&self) -> bool;
fn has_restrict(&self) -> bool;
fn has_rpath(&self) -> bool;
}
impl Properties for MachO<'_> {
fn has_arc(&self) -> bool {
if let Ok(imports) = self.imports() {
for import in &imports {
if import.name == "_objc_release" {
return true;
}
}
}
false
}
fn has_canary(&self) -> bool {
if let Ok(imports) = self.imports() {
for import in &imports {
match import.name {
"___stack_chk_fail" | "___stack_chk_guard" => return true,
_ => continue,
}
}
}
false
}
fn has_code_signature(&self) -> bool {
for loadcmd in &self.load_commands {
if let CommandVariant::CodeSignature(cmd) = loadcmd.command {
if cmd.datasize > 0 {
return true;
}
}
}
false
}
fn has_encrypted(&self) -> bool {
for loadcmd in &self.load_commands {
match loadcmd.command {
CommandVariant::EncryptionInfo32(cmd) => {
if cmd.cryptid != 0 {
return true;
}
}
CommandVariant::EncryptionInfo64(cmd) => {
if cmd.cryptid != 0 {
return true;
}
}
_ => (),
}
}
false
}
fn has_fortify(&self) -> bool {
for sym in self.symbols().flatten() {
if sym.0.ends_with("_chk") {
return true;
}
}
false
}
fn has_fortified(&self) -> u32 {
let mut fortified_count: u32 = 0;
for sym in self.symbols().flatten() {
if sym.0.ends_with("_chk") {
fortified_count += 1;
}
}
fortified_count
}
fn has_nx_heap(&self) -> bool {
matches!(self.header.flags & MH_NO_HEAP_EXECUTION, x if x != 0)
}
fn has_nx_stack(&self) -> bool {
!matches!(self.header.flags & MH_ALLOW_STACK_EXECUTION, x if x != 0)
}
fn has_pie(&self) -> bool {
matches!(self.header.flags & MH_PIE, x if x != 0)
}
fn has_restrict(&self) -> bool {
for segment in &self.segments {
if let Ok(name) = segment.name() {
if name.to_string().to_lowercase() == "__restrict" {
return true;
}
}
}
false
}
fn has_rpath(&self) -> bool {
for loadcmd in &self.load_commands {
if let CommandVariant::Rpath(_) = loadcmd.command {
return true;
}
}
false
}
}