checksec 0.0.9

Fast multi-platform (ELF/PE/MachO) binary checksec command line utility and library.
Documentation
//! Implements checksec for `MachO` binaries
#[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;
//use crate::shared::{Rpath, VecRpath};

const MH_ALLOW_STACK_EXECUTION: u32 = 0x0002_0000;
const MH_PIE: u32 = 0x0020_0000;
const MH_NO_HEAP_EXECUTION: u32 = 0x0100_0000;

/// Checksec result struct for `MachO32/64` binaries
///
/// **Example**
///
/// ```rust
/// use checksec::macho::CheckSecResults;
/// use goblin::mach::MachO;
/// use std::fs;
///
/// pub fn print_results(binary: &String) {
///     if let Ok(buf) = fs::read(binary) {
///         if let Ok(macho) = MachO::parse(&buf, 0) {
///             println!("{:#?}", CheckSecResults::parse(&macho));
///         }
///     }
/// }
/// ```
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Deserialize, Serialize)]
pub struct CheckSecResults {
    /// Automatic Reference Counting
    pub arc: bool,
    /// Stack Canary
    pub canary: bool,
    /// Code Signature (codesign)
    pub code_signature: bool,
    /// Encrypted (`LC_ENCRYPTION_INFO`/`LC_ENCRYPTION_INFO_64`)
    pub encrypted: bool,
    /// Fortify (*CFLAGS=*`-D_FORTIFY_SOURCE`)
    pub fortify: bool,
    /// Fortified functions
    pub fortified: u32,
    /// Non-Executable Heap (`MH_NO_HEAP_EXECUTION`)
    pub nx_heap: bool,
    /// Non-Executable Stack (`MH_ALLOW_STACK_EXECUTION`)
    pub nx_stack: bool,
    /// Position Independent Executable (`MH_PIE`)
    pub pie: bool,
    /// Restrict segment
    pub restrict: bool,
    /// Load Command @rpath
    //rpath: VecRpath,
    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"))]
    /// Colorized human readable format output
    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")]
    /// Colorized human readable format output
    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(),
            //self.rpath
            colorize_bool!(self.rpath)
        )
    }
}

/// checksec Trait implementation for
/// [`goblin::mach::MachO`](https://docs.rs/goblin/latest/goblin/mach/struct.MachO.html)
///
/// **Example**
///
/// ```rust
/// use checksec::macho::Properties;
/// use goblin::mach::MachO;
/// use std::fs;
///
/// pub fn print_results(binary: &String) {
///     if let Ok(buf) = fs::read(binary) {
///         if let Ok(macho) = MachO::parse(&buf, 0) {
///             println!("arc: {}", macho.has_arc());
///         }
///     }
/// }
/// ```
pub trait Properties {
    /// check import names for `_objc_release`
    fn has_arc(&self) -> bool;
    /// check import names for `___stack_chk_fail` or `___stack_chk_guard`
    fn has_canary(&self) -> bool;
    /// check data size of code signature in load commands
    fn has_code_signature(&self) -> bool;
    /// check if `cryptid` has a value set for EncryptionInfo32/64 in load
    /// commands
    fn has_encrypted(&self) -> bool;
    /// check for symbols ending in `_chk` from symbols
    fn has_fortify(&self) -> bool;
    /// count symbols ending in `_chk` from symbols
    fn has_fortified(&self) -> u32;
    /// check `MH_NO_HEAP_EXECUTION` *(0x01000000)* in `MachO` header flags
    fn has_nx_heap(&self) -> bool;
    /// check `MH_ALLOW_STACK_EXECUTION` *(0x00020000)* in `MachO` header flags
    fn has_nx_stack(&self) -> bool;
    /// check `MH_PIE` *(0x00200000)* in `MachO` header flags
    fn has_pie(&self) -> bool;
    /// check for `___restrict` segment name
    fn has_restrict(&self) -> bool;
    //fn has_rpath(&self) -> VecRpath;
    /// check for `RPath` in load commands
    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 {
                // just check for existence, todo full validation
                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) -> VecRpath {
    fn has_rpath(&self) -> bool {
        // simply check for existence of @rpath command for now
        // parse out rpath entries similar to elf later
        // paths separated by `;` instead of `:` like the elf counterpart
        for loadcmd in &self.load_commands {
            if let CommandVariant::Rpath(_) = loadcmd.command {
                return true;
                //return VecRpath::new(vec![Rpath::Yes("true".to_string())]);
            }
        }
        //VecRpath::new(vec![Rpath::None])
        false
    }
}