reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: analyzing-mach-o-binaries-on-macos
|
||||
description: 'Statically analyzes macOS Mach-O malware: parsing the header and load commands,
|
||||
handling fat/universal binaries, reading linked dylibs and entitlements, and checking code
|
||||
signatures to infer capability and trust. Activates for requests to analyze a Mach-O binary,
|
||||
inspect macOS malware, or parse load commands and entitlements.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- mach-o
|
||||
- macos
|
||||
- static-analysis
|
||||
- code-signing
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059.002
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Apple Mach-O file format reference — https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h'
|
||||
- 'macOS code signing overview — https://developer.apple.com/documentation/security/code_signing_services'
|
||||
---
|
||||
|
||||
# Analyzing Mach-O Binaries on macOS
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a macOS sample (Mach-O) and need a static capability and trust read.
|
||||
- You must handle a fat/universal binary containing multiple architecture slices.
|
||||
- You need to inspect linked dylibs, entitlements, and code-signing status.
|
||||
|
||||
**Do not use** Windows PE tooling on Mach-O — the formats differ entirely; use Mach-O-aware
|
||||
parsers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Mach-O parser (Python stdlib `struct`, or `macholib`/LIEF); the sample handled inertly.
|
||||
- For signing/entitlements on macOS, `codesign`/`otool` are authoritative.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Parse statically; never execute the sample, especially on a real macOS host.
|
||||
- Keep the sample password-protected at rest and reference it by hash.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Detect fat vs. thin and architecture
|
||||
|
||||
Check the magic: `0xCAFEBABE` (fat/universal) vs. `0xFEEDFACE`/`0xFEEDFACF` (Mach-O 32/64). For
|
||||
fat binaries, enumerate and analyze each slice.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py header sample.macho
|
||||
```
|
||||
|
||||
### Step 2: Parse load commands
|
||||
|
||||
Read load commands for linked dylibs (`LC_LOAD_DYLIB`), entry point (`LC_MAIN`), and signing
|
||||
(`LC_CODE_SIGNATURE`). The dylib list hints at capability (networking, crypto).
|
||||
|
||||
### Step 3: Inspect entitlements and signing
|
||||
|
||||
On macOS, use `codesign`/`otool` to read entitlements and verify the signature. Ad-hoc or absent
|
||||
signatures and suspicious entitlements are risk indicators.
|
||||
|
||||
### Step 4: Infer capability and route
|
||||
|
||||
Map linked frameworks/symbols to behaviors and route to disassembly/RE for deeper analysis.
|
||||
|
||||
## Validation
|
||||
|
||||
- Fat binaries are decomposed and each slice is analyzed, not just the first.
|
||||
- Load commands, dylibs, and signing status are enumerated correctly.
|
||||
- Capability inferences are corroborated by linked frameworks/symbols.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Analyzing only one slice of a universal binary.
|
||||
- Trusting a present signature without verifying it (ad-hoc signatures verify but aren't trusted).
|
||||
- Applying PE assumptions (sections/imports) to Mach-O structures.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the Mach-O header parser.
|
||||
- Apple Mach-O loader.h and code signing docs (linked in frontmatter).
|
||||
@@ -0,0 +1,42 @@
|
||||
# API Reference: Analyzing Mach-O Binaries on macOS
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses Mach-O headers with stdlib `struct`: it detects fat/universal vs. thin
|
||||
binaries, enumerates slices, and reads load commands for linked dylibs, the entry point, and the
|
||||
presence of a code signature. It parses statically and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| codesign | Verify signature and read entitlements on macOS |
|
||||
| otool | Inspect load commands and linked libraries |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `parse(path)`
|
||||
Dispatches on magic to handle fat vs. thin binaries; returns format, slices, and per-slice data.
|
||||
|
||||
### `parse_thin(data, base)`
|
||||
Parses one Mach-O image's load commands (dylibs, LC_MAIN, LC_CODE_SIGNATURE).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py header sample.macho
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Apple Mach-O `loader.h` (header and load-command structures) —
|
||||
https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
|
||||
- macOS code signing overview —
|
||||
https://developer.apple.com/documentation/security/code_signing_services
|
||||
- Fat/universal binary layout (FAT_MAGIC + slice table), Mach-O format documentation
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a Mach-O (or fat/universal) binary header and key load commands.
|
||||
|
||||
Detects fat vs. thin Mach-O, enumerates architecture slices, and parses load commands for linked
|
||||
dylibs, the main entry point, and the presence of a code signature. Uses stdlib ``struct`` only.
|
||||
It parses bytes statically and never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
MH_MAGIC = 0xFEEDFACE
|
||||
MH_MAGIC_64 = 0xFEEDFACF
|
||||
MH_CIGAM = 0xCEFAEDFE
|
||||
MH_CIGAM_64 = 0xCFFAEDFE
|
||||
FAT_MAGIC = 0xCAFEBABE
|
||||
FAT_CIGAM = 0xBEBAFECF
|
||||
|
||||
LC_LOAD_DYLIB = 0x0C
|
||||
LC_MAIN = 0x80000028
|
||||
LC_CODE_SIGNATURE = 0x1D
|
||||
|
||||
|
||||
def _u32(data: bytes, off: int, be: bool) -> int:
|
||||
return struct.unpack_from(">I" if be else "<I", data, off)[0]
|
||||
|
||||
|
||||
def parse_thin(data: bytes, base: int) -> dict:
|
||||
magic = struct.unpack_from(">I", data, base)[0]
|
||||
be = magic in (MH_MAGIC, MH_MAGIC_64)
|
||||
is64 = magic in (MH_MAGIC_64, MH_CIGAM_64)
|
||||
hdr = base
|
||||
ncmds = _u32(data, hdr + 16, be)
|
||||
off = hdr + (32 if is64 else 28)
|
||||
dylibs, signed, entry = [], False, None
|
||||
for _ in range(ncmds):
|
||||
cmd = _u32(data, off, be)
|
||||
cmdsize = _u32(data, off + 4, be)
|
||||
if cmdsize == 0:
|
||||
break
|
||||
if cmd == LC_LOAD_DYLIB:
|
||||
name_off = _u32(data, off + 8, be)
|
||||
raw = data[off + name_off:off + cmdsize].split(b"\x00", 1)[0]
|
||||
dylibs.append(raw.decode("latin-1"))
|
||||
elif cmd == (LC_MAIN & 0xFFFFFFFF) or cmd == LC_MAIN:
|
||||
entry = "LC_MAIN"
|
||||
elif cmd == LC_CODE_SIGNATURE:
|
||||
signed = True
|
||||
off += cmdsize
|
||||
return {"arch": "64-bit" if is64 else "32-bit", "dylibs": dylibs,
|
||||
"has_code_signature": signed, "entry": entry}
|
||||
|
||||
|
||||
def parse(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
magic = struct.unpack_from(">I", data, 0)[0]
|
||||
if magic in (FAT_MAGIC, FAT_CIGAM):
|
||||
nfat = struct.unpack_from(">I", data, 4)[0]
|
||||
slices = []
|
||||
for i in range(nfat):
|
||||
entry_off = 8 + i * 20
|
||||
offset = struct.unpack_from(">I", data, entry_off + 8)[0]
|
||||
slices.append(parse_thin(data, offset))
|
||||
return {"format": "fat/universal", "slice_count": nfat, "slices": slices}
|
||||
if magic in (MH_MAGIC, MH_MAGIC_64, MH_CIGAM, MH_CIGAM_64):
|
||||
return {"format": "thin Mach-O", **parse_thin(data, 0)}
|
||||
return {"error": "not a Mach-O binary"}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Parse Mach-O header/load commands")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("header")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(parse(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user