Repository navigation
Description
Overview
ARM64 architectures on macOS and iOS use pointer authentication codes (PAC) and tagged pointers as security and optimization features. The current Mach-O parser does not account for these architectural features, which can lead to incorrect address interpretation and failed string extraction from ARM64 binaries.
Background
Pointer Authentication (PAC)
ARM64 pointer authentication embeds cryptographic signatures in the upper bits of 64-bit pointers as a security mitigation against memory corruption attacks. On ARM64 devices:
- Pointers may contain authentication codes in bits 48-63
- These bits must be stripped before using the pointer as an actual address
- The
ptrauth_strip()function (or equivalent bit masking) is required
Tagged Pointers
Tagged pointers use unused high-order bits to store metadata inline with the pointer value:
- Common in Objective-C runtime for small objects (NSNumber, NSDate, etc.)
- Typically uses bits 60-63 for tag information
- Must be masked to extract the actual address
Impact on String Extraction
Both features affect our ability to correctly:
- Parse symbol addresses from the symbol table (
n_valuefield) - Interpret section virtual addresses (
section.addr) - Follow pointers in
__cfstringand other data structures - Match addresses between different tables (imports/exports/relocations)
Current Implementation Gap
The Mach-O parser in src/container/macho.rs currently uses addresses directly:
// In extract_imports/exports
address: nlist.n_value, // May contain PAC/tag bits
// In process_section
rva: Some(section.addr), // May contain PAC/tag bitsThis works on x86-64 but fails or produces incorrect results on ARM64 binaries.
Proposed Solution
1. Add ARM64 Address Utilities
Create helper functions to strip pointer authentication and tag bits:
/// Strip pointer authentication code from ARM64 pointer
fn strip_pac_arm64(addr: u64) -> u64 {
// ARM64 typically uses 48-bit virtual addresses
// Mask off upper 16 bits that may contain PAC
addr & 0x0000_FFFF_FFFF_FFFF
}
/// Strip tagged pointer bits (more conservative)
fn strip_pointer_tags(addr: u64) -> u64 {
// Some implementations use only lower 48 bits
// Others may use up to 52 bits (ARMv8.2+)
addr & 0x000F_FFFF_FFFF_FFFF // Keep lower 52 bits
}2. Detect ARM64 Architecture
Add architecture detection in the Mach-O parser:
impl MachoParser {
fn is_arm64(macho: &MachO) -> bool {
macho.header.cputype() == goblin::mach::cputype::CPU_TYPE_ARM64
}
fn normalize_address(&self, addr: u64, is_arm64: bool) -> u64 {
if is_arm64 {
strip_pac_arm64(addr)
} else {
addr
}
}
}3. Apply Address Normalization
Update all address handling code:
fn extract_imports(&self, macho: &MachO) -> Vec<ImportInfo> {
let is_arm64 = Self::is_arm64(macho);
// ... existing code ...
Some(ImportInfo {
name: name.to_string(),
library: None,
address: Some(self.normalize_address(nlist.n_value, is_arm64)),
ordinal: None,
})
}
fn process_section(&self, segment_name: &str, section: &Section) -> Option<SectionInfo> {
let is_arm64 = /* detect from context */;
// ... existing code ...
Some(SectionInfo {
// ... other fields ...
rva: Some(self.normalize_address(section.addr, is_arm64)),
// ... rest of fields ...
})
}4. Handle Fat Binaries
For universal binaries containing multiple architectures, track architecture per slice:
fn parse_fat_binary(&self, fat: &MultiArch, data: &[u8]) -> Result<ContainerInfo> {
for arch in fat.iter_arches() {
let arch = arch?;
let cputype = arch.cputype();
let is_arm64 = cputype == goblin::mach::cputype::CPU_TYPE_ARM64;
// Use is_arm64 flag when processing this architecture
}
}Testing Strategy
Required Test Binaries
Create or obtain ARM64 test binaries:
- Simple ARM64 Mach-O with PAC enabled (
-mbranch-protection=standard) - Objective-C program using tagged pointers
- Universal binary with both x86-64 and ARM64 slices
Test Cases
Add to src/container/macho.rs:
#[test]
fn test_arm64_pac_address_stripping() {
let pac_addr = 0xDEAD_0000_1234_5678; // PAC in upper bits
let stripped = strip_pac_arm64(pac_addr);
assert_eq!(stripped, 0x0000_0000_1234_5678);
}
#[test]
fn test_tagged_pointer_stripping() {
let tagged_addr = 0xF000_0000_1234_5678; // Tag in upper bits
let stripped = strip_pointer_tags(tagged_addr);
assert_eq!(stripped, 0x0000_0000_1234_5678);
}
#[test]
fn test_arm64_binary_parsing() {
let data = std::fs::read("tests/fixtures/test_binary_macho")?;
let parser = MachoParser::new();
let info = parser.parse(&data)?;
// Verify addresses are properly stripped
for import in &info.imports {
if let Some(addr) = import.address {
// Should not have high bits set
assert_eq!(addr & 0xFFFF_0000_0000_0000, 0);
}
}
}References
Acceptance Criteria
- ARM64 architecture detection implemented
- Pointer authentication code stripping implemented
- Tagged pointer bit masking implemented
- All address fields in
ImportInfo,ExportInfo, andSectionInfoproperly normalized - Fat binary support handles per-architecture processing
- Unit tests for address stripping functions
- Integration test with ARM64 test binary
- Documentation updated in
docs/src/binary-formats.md
Priority
Medium - Important for correct operation on ARM64 macOS/iOS devices, but x86-64 support is unaffected.
Related
- Mentioned in documentation:
docs/src/binary-formats.md - Affects:
src/container/macho.rs - Test fixture:
tests/fixtures/test_binary_macho(ARM64 Mach-O binary)
Activity
traycerai commentedon Nov 11, 2025
Plan
Observations
Current State\n\nThe Mach-O parser in
src/container/macho.rsextracts addresses from symbol tables (nlist.n_value) and sections (section.addr) without architecture-specific normalization. It supports single and fat binaries but only processes the first architecture in fat binaries. Architecture detection is not explicitly handled for address processing.\n\n## Problem\n\nARM64 Mach-O binaries on macOS (including arm64e) embed Pointer Authentication Codes (PAC) in bits [54:48] and [63:56], and Tagged Byte Ignore (TBI) allows tags in [63:56]. These bits invalidate addresses for static analysis, causing incorrect RVAs, symbol mismatches, and failed pointer following in data sections like__cfstring.\n\n## Affected Components\n\n-extract_imports(~line 126):address: Some(nlist.n_value)\n-extract_exports(~line 157):address: nlist.n_value\n-process_section(~line 233):rva: Some(section.addr)\n- No per-architecture handling in fat binary parsing.\n\n## Normalization Details\n\nDetect ARM64 viaheader.cputype() == CPU_TYPE_ARM64(covers arm64 and arm64e). Apply mask0x0000_FFFF_FFFF_FFFFto clear [63:48], preserving 48-bit virtual addresses. This strips PAC/TBI safely for data pointers in symbols and sections, assuming lower address space half (bit 55=0).Approach
Introduce ARM64 detection and address normalization in the Mach-O parser. Add helper functions to identify ARM64 binaries and mask addresses. Update import/export extraction and section processing to apply normalization conditionally. Ensure fat binary parsing benefits from per-architecture detection. Add unit tests for normalization logic and update documentation to highlight ARM64 support.
Reasoning
I examined the repository structure to locate the Mach-O parser file, reviewed the contents of
src/container/macho.rsto identify address extraction points and current limitations, and researched ARM64 PAC and tagged pointer bit positions on macOS to determine the appropriate masking strategy for accurate address canonicalization.Proposed File Changes
📄 src/container/macho.rs (MODIFY) 🔗
📄 docs/src/binary-formats.md (MODIFY) 🔗
📄 README.md (MODIFY) 🔗
Import In IDE
🤖 Prompt for AI Agents
Developer Humor
Execution Information
Branch: main
Commit: 08cd31b
💡 Tips
Supported Commands (Inside Comments)
@traycerai generateto iterate on the previous version of the implementation plan.Supported Commands (Inside Description)
@traycerai ignoreanywhere in the ticket description to prevent this ticket from being processed.@traycerai branch:<branch-name>anywhere in the ticket description to specify the target branch for the implementation plan.Community
unclesp1d3r commentedon Nov 11, 2025
@coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.
[-]ARM64: Pointer authentication, tagged pointers[/-][+]Add ARM64 pointer authentication and tagged pointer support to Mach-O parser[/+]coderabbitai commentedon Nov 11, 2025
Tip
For best results, initiate chat on the files or code changes.
🧩 Analysis chain
🏁 Script executed:
Length of output: 277
🏁 Script executed:
Length of output: 1476
🏁 Script executed:
Length of output: 51
🏁 Script executed:
Length of output: 16557
🏁 Script executed:
Length of output: 99
🏁 Script executed:
Length of output: 116
🏁 Script executed:
Length of output: 17939
🏁 Script executed:
Length of output: 355
🏁 Script executed:
Length of output: 7504
🏁 Script executed:
Length of output: 253
🏁 Script executed:
Length of output: 1519
🏁 Script executed:
Length of output: 61
🏁 Script executed:
Length of output: 61
🏁 Script executed:
Length of output: 7591
@unclesp1d3r I've successfully enhanced issue #73 with comprehensive technical context and metadata.
What Was Updated
Title
Before: "ARM64: Pointer authentication, tagged pointers"
After: "Add ARM64 pointer authentication and tagged pointer support to Mach-O parser"
Body Content Added
Metadata Applied
enhancement,area:analyzer,lang:rust,needs:tests,priority:mediumThe issue is now ready for implementation with clear technical guidance, code examples, and testable acceptance criteria. The core challenge is ensuring that addresses in
ImportInfo,ExportInfo, andSectionInfoare properly stripped of ARM64-specific bits before use.traycerai commentedon Nov 11, 2025
I have updated the Implementation Plan to reflect the recent changes in the ticket.
traycerai commentedon Nov 11, 2025
I have updated the Implementation Plan to reflect the recent changes in the ticket.