use core::marker::PhantomData;
use bitflags::bitflags;
use goblin::mach::segment::{
Section as GoblinSection, SectionIterator as GoblinSectionIter, Segment as GoblinSegment,
};
use crate::util::cstr_from_fixed;
const SECTION_TYPE_MASK: u32 = 0x0000_00ff;
pub struct Segment<'a, 'p> {
inner: &'p GoblinSegment<'a>,
}
impl<'a, 'p> Segment<'a, 'p> {
pub(crate) fn new(inner: &'p GoblinSegment<'a>) -> Self {
Self { inner }
}
pub fn name(&self) -> &str {
cstr_from_fixed(&self.inner.segname)
}
pub fn vmaddr(&self) -> u64 {
self.inner.vmaddr
}
pub fn vmsize(&self) -> u64 {
self.inner.vmsize
}
pub fn fileoff(&self) -> u64 {
self.inner.fileoff
}
pub fn filesize(&self) -> u64 {
self.inner.filesize
}
pub fn maxprot(&self) -> u32 {
self.inner.maxprot
}
pub fn initprot(&self) -> u32 {
self.inner.initprot
}
pub fn nsects(&self) -> u32 {
self.inner.nsects
}
pub fn flags(&self) -> u32 {
self.inner.flags
}
pub fn sections(&self) -> SectionIter<'a, 'p> {
SectionIter {
segments: SegmentSource::Single(Some(self.inner)),
current: None,
_parent: PhantomData,
}
}
pub fn body(&self) -> &'a [u8] {
self.inner.data
}
}
impl core::fmt::Debug for Segment<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Segment")
.field("name", &self.name())
.field("vmaddr", &self.vmaddr())
.field("vmsize", &self.vmsize())
.field("fileoff", &self.fileoff())
.field("filesize", &self.filesize())
.field("nsects", &self.nsects())
.field("flags", &format_args!("0x{:x}", self.flags()))
.finish()
}
}
pub struct SegmentIter<'a, 'p> {
inner: core::slice::Iter<'p, GoblinSegment<'a>>,
}
impl<'a, 'p> SegmentIter<'a, 'p> {
pub(crate) fn new(slice: &'p [GoblinSegment<'a>]) -> Self {
Self {
inner: slice.iter(),
}
}
}
impl<'a, 'p> Iterator for SegmentIter<'a, 'p> {
type Item = Segment<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(Segment::new)
}
}
pub struct Section<'a, 'p> {
inner: GoblinSection,
body: &'a [u8],
_parent: PhantomData<&'p ()>,
}
impl<'a, 'p> Section<'a, 'p> {
pub fn segname(&self) -> &str {
cstr_from_fixed(&self.inner.segname)
}
pub fn sectname(&self) -> &str {
cstr_from_fixed(&self.inner.sectname)
}
pub fn addr(&self) -> u64 {
self.inner.addr
}
pub fn size(&self) -> u64 {
self.inner.size
}
pub fn offset(&self) -> u32 {
self.inner.offset
}
pub fn align(&self) -> u32 {
self.inner.align
}
pub fn reloff(&self) -> u32 {
self.inner.reloff
}
pub fn nreloc(&self) -> u32 {
self.inner.nreloc
}
pub fn flags(&self) -> u32 {
self.inner.flags
}
pub fn section_type(&self) -> SectionType {
SectionType::from_raw(self.inner.flags & SECTION_TYPE_MASK)
}
pub fn attributes(&self) -> SectionAttributes {
SectionAttributes::from_bits_retain(self.inner.flags & !SECTION_TYPE_MASK)
}
pub fn holds_instructions(&self) -> bool {
self.attributes()
.intersects(SectionAttributes::PURE_INSTRUCTIONS | SectionAttributes::SOME_INSTRUCTIONS)
}
pub fn body(&self) -> &'a [u8] {
self.body
}
pub fn shannon_entropy(&self) -> f64 {
shannon_entropy(self.body)
}
pub fn blake3(&self) -> blake3::Hash {
blake3::hash(self.body)
}
}
impl core::fmt::Debug for Section<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Section")
.field("segname", &self.segname())
.field("sectname", &self.sectname())
.field("addr", &self.addr())
.field("size", &self.size())
.field("offset", &self.offset())
.field("align", &self.align())
.field("flags", &format_args!("0x{:x}", self.flags()))
.finish()
}
}
enum SegmentSource<'a, 'p> {
All(core::slice::Iter<'p, GoblinSegment<'a>>),
Single(Option<&'p GoblinSegment<'a>>),
}
impl<'a, 'p> SegmentSource<'a, 'p> {
fn next(&mut self) -> Option<&'p GoblinSegment<'a>> {
match self {
Self::All(it) => it.next(),
Self::Single(slot) => slot.take(),
}
}
}
pub struct SectionIter<'a, 'p> {
segments: SegmentSource<'a, 'p>,
current: Option<GoblinSectionIter<'a>>,
_parent: PhantomData<&'p ()>,
}
impl<'a, 'p> SectionIter<'a, 'p> {
pub(crate) fn new(slice: &'p [GoblinSegment<'a>]) -> Self {
Self {
segments: SegmentSource::All(slice.iter()),
current: None,
_parent: PhantomData,
}
}
}
impl<'a, 'p> Iterator for SectionIter<'a, 'p> {
type Item = Section<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(it) = &mut self.current {
match it.next() {
Some(Ok((section, data))) => {
return Some(Section {
inner: section,
body: data,
_parent: PhantomData,
});
}
Some(Err(_)) => {
continue;
}
None => {
self.current = None;
}
}
}
let next_segment = self.segments.next()?;
self.current = Some(next_segment.into_iter());
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionType {
Regular,
ZeroFill,
CStringLiterals,
FourByteLiterals,
EightByteLiterals,
LiteralPointers,
NonLazySymbolPointers,
LazySymbolPointers,
SymbolStubs,
ModInitFuncPointers,
ModTermFuncPointers,
Coalesced,
GbZeroFill,
Interposing,
SixteenByteLiterals,
DtraceDof,
LazyDylibSymbolPointers,
ThreadLocalRegular,
ThreadLocalZeroFill,
ThreadLocalVariables,
ThreadLocalVariablePointers,
ThreadLocalInitFunctionPointers,
Other(u32),
}
impl SectionType {
pub fn from_raw(v: u32) -> Self {
match v {
0x00 => Self::Regular,
0x01 => Self::ZeroFill,
0x02 => Self::CStringLiterals,
0x03 => Self::FourByteLiterals,
0x04 => Self::EightByteLiterals,
0x05 => Self::LiteralPointers,
0x06 => Self::NonLazySymbolPointers,
0x07 => Self::LazySymbolPointers,
0x08 => Self::SymbolStubs,
0x09 => Self::ModInitFuncPointers,
0x0a => Self::ModTermFuncPointers,
0x0b => Self::Coalesced,
0x0c => Self::GbZeroFill,
0x0d => Self::Interposing,
0x0e => Self::SixteenByteLiterals,
0x0f => Self::DtraceDof,
0x10 => Self::LazyDylibSymbolPointers,
0x11 => Self::ThreadLocalRegular,
0x12 => Self::ThreadLocalZeroFill,
0x13 => Self::ThreadLocalVariables,
0x14 => Self::ThreadLocalVariablePointers,
0x15 => Self::ThreadLocalInitFunctionPointers,
other => Self::Other(other),
}
}
pub fn is_zero_fill(self) -> bool {
matches!(
self,
Self::ZeroFill | Self::GbZeroFill | Self::ThreadLocalZeroFill
)
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectionAttributes: u32 {
const PURE_INSTRUCTIONS = 0x8000_0000;
const NO_TOC = 0x4000_0000;
const STRIP_STATIC_SYMS = 0x2000_0000;
const NO_DEAD_STRIP = 0x1000_0000;
const LIVE_SUPPORT = 0x0800_0000;
const SELF_MODIFYING_CODE = 0x0400_0000;
const DEBUG_SECTION = 0x0200_0000;
const SOME_INSTRUCTIONS = 0x0000_0400;
const EXT_RELOC = 0x0000_0200;
const LOC_RELOC = 0x0000_0100;
}
}
fn shannon_entropy(body: &[u8]) -> f64 {
if body.is_empty() {
return 0.0;
}
let mut counts = [0u64; 256];
for &b in body {
if let Some(slot) = counts.get_mut(b as usize) {
*slot = slot.saturating_add(1);
}
}
let len = body.len() as f64;
let mut entropy = 0.0f64;
for &c in &counts {
if c > 0 {
let p = (c as f64) / len;
entropy -= p * p.log2();
}
}
entropy
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn section_type_decodes_known_kinds() {
assert!(matches!(SectionType::from_raw(0x00), SectionType::Regular));
assert!(matches!(
SectionType::from_raw(0x02),
SectionType::CStringLiterals
));
assert!(matches!(
SectionType::from_raw(0x08),
SectionType::SymbolStubs
));
}
#[test]
fn section_type_zero_fill_predicate() {
assert!(SectionType::from_raw(0x01).is_zero_fill());
assert!(SectionType::from_raw(0x0c).is_zero_fill());
assert!(SectionType::from_raw(0x12).is_zero_fill());
assert!(!SectionType::from_raw(0x00).is_zero_fill());
assert!(!SectionType::from_raw(0x02).is_zero_fill());
}
#[test]
fn shannon_entropy_uniform_byte_pool_max_8() {
let body: Vec<u8> = (0u16..256).map(|x| x as u8).collect();
let h = shannon_entropy(&body);
assert!((h - 8.0).abs() < 1e-9, "expected entropy ≈ 8, got {h}");
}
#[test]
fn shannon_entropy_constant_byte_zero() {
let body = [0xaau8; 1024];
assert_eq!(shannon_entropy(&body), 0.0);
}
#[test]
fn shannon_entropy_empty_is_zero() {
assert_eq!(shannon_entropy(&[]), 0.0);
}
}