Last active
October 1, 2026 10:40
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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