Skip to content

Instantly share code, notes, and snippets.

@vadim-a-yegorov
Last active October 1, 2026 10:40
Show Gist options
  • Select an option

  • Save vadim-a-yegorov/c507289189eb3cf14b01672612e2051a to your computer and use it in GitHub Desktop.

Select an option

Save vadim-a-yegorov/c507289189eb3cf14b01672612e2051a to your computer and use it in GitHub Desktop.
Mach-O, ELF, PE/PE32+ — headers, entry point, segments/sections, symbols, imports/exports, dynamic-link info, relocations, code signature, identify the container, walk its tables, locate segments and the pack/crypt markers.
formats
1 Mach-O (Darwin) †loader.h
Magic: fat 0xcafebabe/0xcafebabef (big-endian, per-slice fat_arch); thin MH_MAGIC_64 0xfeedfacf. mach_header_64 = { magic, cputype, cpusubtype, filetype (MH_EXECUTE/MH_DYLIB/MH_BUNDLE/MH_KEXT_BUNDLE), ncmds, sizeofcmds, flags, reserved }. Walk ncmds load commands, each a {uint32 cmd; uint32 cmdsize;} header; advance by cmdsize.
- LC_SEGMENT_64 (0x19) → segment_command_64 = { cmd, cmdsize, char segname[16], uint64 vmaddr, vmsize, fileoff, filesize, int maxprot, initprot, uint32 nsects, flags } then nsects × section_64 { char sectname[16], segname[16], uint64 addr, size, uint32 offset, align, reloff, nreloc, flags, reserved1,2,3 }. __TEXT.__text is the code; vmaddr of __TEXT is the slide base.
- LC_SYMTAB (0x2) symoff/nsyms/stroff; LC_DYSYMTAB (0xb) indirect-symbol table (lazy/nonlazy pointer names).
- LC_DYLD_INFO_ONLY (0x80000022) rebase/bind/lazy_bind/export opcode streams; on arm64e LC_DYLD_CHAINED_FIXUPS (0x80000034) + LC_DYLD_EXPORTS_TRIE.
- LC_MAIN (0x80000028) entryoff (file-relative entry); older LC_UNIXTHREAD carries the start register state.
- LC_LOAD_DYLIB (0xc) dependency path; LC_UUID; LC_CODE_SIGNATURE (0x1d) → a SuperBlob (CodeDirectory, entitlements, CMS).
- LC_ENCRYPTION_INFO_64 (0x2c) → encryption_info_command_64 = { cmd, cmdsize, uint32 cryptoff, cryptsize, cryptid, pad }. **cryptid != 0 ⇒ the [cryptoff, cryptoff+cryptsize) file range is FairPlay ciphertext** (see dynamic-unpacking). cryptid == 0 ⇒ plain.
#include <mach-o/loader.h>
#include <mach-o/fat.h>
struct mach_header_64 *mh = (void *)p;
struct load_command *lc = (void *)(mh + 1);
for (uint32_t i = 0; i < mh->ncmds; i++) {
if (lc->cmd == LC_SEGMENT_64) { struct segment_command_64 *s = (void *)lc; }
else if (lc->cmd == LC_ENCRYPTION_INFO_64) { struct encryption_info_command_64 *e = (void *)lc; }
else if (lc->cmd == LC_MAIN) { struct entry_point_command *m = (void *)lc; }
lc = (void *)((uint8_t *)lc + lc->cmdsize);
}
Tools: otool -lhV, nm -m, dyld_info -fixups -exports, llvm-objdump --macho, codesign -dvvv, rabin2 -I/-l/-s, LIEF.
2 ELF (Linux / BSD / Android) †uapi/linux/elf.h
e_ident[0..3] = 0x7f 'E' 'L' 'F', [4] class (ELFCLASS64=2), [5] data. Elf64_Ehdr = { e_ident[16], uint16 e_type (ET_EXEC=2 fixed-base, ET_DYN=3 PIE/.so), e_machine, uint32 e_version, Elf64_Addr e_entry, Elf64_Off e_phoff, e_shoff, uint32 e_flags, uint16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx }.
Program headers (loader view) Elf64_Phdr = { uint32 p_type (PT_LOAD=1, PT_DYNAMIC=2, PT_INTERP=3, PT_GNU_STACK), p_flags (R=4/W=2/X=1), Elf64_Off p_offset, Elf64_Addr p_vaddr, p_paddr, uint64 p_filesz, p_memsz, p_align }. Load base = lowest PT_LOAD p_vaddr; runtime slide added for ET_DYN.
Section headers (link view) Elf64_Shdr: .text, .dynsym/.dynstr, .symtab/.strtab, .rela.plt/.rela.dyn, .got/.got.plt, .plt. .dynamic = array of Elf64_Dyn {Sxword d_tag; union d_val/d_ptr;} — DT_NEEDED, DT_STRTAB, DT_SYMTAB, DT_PLTGOT, DT_JMPREL. Imports resolve through .rela.plt Elf64_Rela {r_offset, r_info, r_addend} (R_X86_64_JUMP_SLOT / R_AARCH64_JUMP_SLOT) into .got.plt.
#include <elf.h>
Elf64_Ehdr *eh = (void *)p;
Elf64_Phdr *ph = (void *)(p + eh->e_phoff);
for (int i = 0; i < eh->e_phnum; i++)
if (ph[i].p_type == PT_LOAD) { /* ph[i].p_vaddr, p_offset, p_filesz, p_flags */ }
Tools: readelf -a / -d / -r, objdump -d, nm -D, eu-readelf, patchelf, rabin2, LIEF.
3 PE / PE32+ (Windows) †winnt.h
IMAGE_DOS_HEADER { uint16 e_magic 'MZ' (0x5A4D), …, int32 e_lfanew }. At e_lfanew: IMAGE_NT_HEADERS64 = { uint32 Signature 'PE\0\0' (0x00004550), IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER64 }. IMAGE_FILE_HEADER = { Machine, uint16 NumberOfSections, TimeDateStamp, PointerToSymbolTable, NumberOfSymbols, uint16 SizeOfOptionalHeader, Characteristics }. IMAGE_OPTIONAL_HEADER64 = { uint16 Magic (0x20b = PE32+), linker vers, SizeOfCode, …, uint32 AddressOfEntryPoint (RVA), BaseOfCode, uint64 ImageBase, uint32 SectionAlignment, FileAlignment, …, SizeOfImage, SizeOfHeaders, …, NumberOfRvaAndSizes, IMAGE_DATA_DIRECTORY DataDirectory[16] }.
Data dirs: EXPORT=0, IMPORT=1, RESOURCE=2, BASERELOC=5, TLS=9, IAT=12. Section table = NumberOfSections × IMAGE_SECTION_HEADER { char Name[8], uint32 VirtualSize, VirtualAddress (RVA), SizeOfRawData, PointerToRawData, …, Characteristics (IMAGE_SCN_MEM_EXECUTE 0x20000000, _WRITE 0x80000000) }.
RVA→file offset: find the section with VirtualAddress ≤ rva < VirtualAddress+VirtualSize; off = rva - VirtualAddress + PointerToRawData. Imports: DataDirectory[1] → IMAGE_IMPORT_DESCRIPTOR { OriginalFirstThunk (INT, names), …, Name, FirstThunk (IAT) }.
#include <windows.h>
IMAGE_DOS_HEADER *dos = (void *)p;
IMAGE_NT_HEADERS64 *nt = (void *)(p + dos->e_lfanew);
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) { /* sec[i].VirtualAddress, PointerToRawData */ }
Tools: dumpbin /headers /imports /exports, PE-bear, pefile (py), LIEF, radare2, llvm-readobj --coff-*.
4 Cross-format RE map
LIEF parses + rewrites all three from one API (Python/C++). radare2/rabin2 and Ghidra/Binary Ninja disassemble all three. capstone = disassembly engine; keystone = assembler; both arch-portable. Per-section Shannon entropy ≈ 8.0 flags a packed/encrypted section — the first tell.
5 Generative hooks
Entry + segment map feeds slide math (process-instrumentation) and the dump-region bounds (dynamic-unpacking). The symbol/import tables name the hook points (hooking-detours) and the exported dispatch of a driver/service (interface-attack-surface). A high-entropy section with a tiny import table and an unusual entry section is a packer — route to dynamic-unpacking.
6 file x; rabin2 -I x → arch/bits/endian/PIE/NX/canary/RELRO/stripped at a glance; pwn checksec x / checksec --file=x same for mitigations.
7 rabin2 -z strings in data, -zz everywhere, -s symbols, -i imports, -E exports, -R relocs, -H header fields, -l linked libs.
8 ELF:
readelf -hSdrW x; nm -DC x (dynamic + demangle); objdump -dM intel x; objdump -s -j .rodata x hexdump a section.
9 Mach-O:
otool -hv/-lv/-L/-IV x; nm -m x; dyld_info -exports -fixups x; codesign -dvvv --entitlements - x; lipo -info x then lipo -thin arm64 x -output x.a64.
10 PE:
dumpbin /headers /imports /exports /dependents x; python -c "import pefile;p=pefile.PE('x');print(hex(p.OPTIONAL_HEADER.AddressOfEntryPoint))".
11 LIEF one-liner:
python -c "import lief;b=lief.parse('x');print(b.entrypoint,[s.name for s in b.sections])".
12 Entropy/packer tell:
binwalk -E x; eyeball rabin2 -S x section sizes; a single RWX segment + tiny imports = packed.
13 Entry point fast:
ELF readelf -h | grep Entry; PE AddressOfEntryPoint; Mach-O LC_MAIN entryoff (otool -l | grep -A2 LC_MAIN).
14 RVA↔offset in r2:
?p vaddr (→paddr) and ?P paddr (→vaddr); rabin2 -O for scripted conversion.
15 Structural diff two builds:
radiff2 -AC a b (function-level), radiff2 -s a b (byte similarity); BinDiff / Diaphora for IDA/Ghidra.
16 Carve firmware/embedded:
binwalk -e fw.bin; unblob fw.bin; unsquashfs/sasquatch for squashfs; cpio -idv for initramfs.
17 Who-calls / one function:
r2 aaa; axt @ sym (xrefs to) and pdf @ sym (disasm one func); agf for its graph.
18 Decompile from CLI:
r2 -A -qc 's main; pdg' x (r2ghidra) or rz-ghidra; Ghidra headless analyzeHeadless proj -import x -postScript.
19 Dynamic-link introspection:
LD_DEBUG=all ./x (resolution trace); LD_TRACE_LOADED_OBJECTS=1 ./x = ldd without execution; ldd -r x.
20 Language recovery:
GoReSym / redress for Go symbols; Rust panic strings carry function paths; strings -t x + grep for build paths.
21 "What is this file":
trid x; file -k x; rabin2 -g x (all info); detect packer die x (Detect It Easy) / yara rules.
22 Section → disk bytes:
dd if=x bs=1 skip=$((off)) count=$((sz)); or r2 pr $sz @ $off > sec.bin.
23 Imports at a glance across formats:
rabin2 -i x works on ELF/PE/Mach-O uniformly.
Sources: Apple mach-o/loader.h (XNU/cctools, APSL) — llvm.org MachO.h; Linux include/uapi/linux/elf.h (GPL-2.0 WITH syscall-note); Microsoft winnt.h IMAGE_* (learn.microsoft.com ns-winnt-image_*); LIEF lief.re; radare2 rada.re (rabin2/radiff2 flags); capstone/keystone capstone-engine.org; binwalk/unblob; Detect-It-Easy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment