Sign in

Binary anatomy

/bin/ls on an Apple Silicon Mac is 154,624 bytes. The machine instructions in it come to 30,363 — 19.6% of the file, spread across two architecture slices:

$ size -m -arch arm64e /bin/ls
Segment __PAGEZERO: 4294967296 (zero fill)
Segment __TEXT: 32768
	Section __text: 15268        ← the instructions
	Section __auth_stubs: 1344
	Section __const: 261
	Section __cstring: 1283
	Section __unwind_info: 208
	total 18364
Segment __DATA_CONST: 16384
	Section __auth_got: 672
	Section __got: 48
	Section __const: 616
	total 1336
Segment __DATA: 16384
	Section __data: 32
	Section __common: 176 (zerofill)
	Section __bss: 344 (zerofill)
	total 552
Segment __LINKEDIT: 32768        ← symbols, fixups, code signature
total 4295065600

size -m -arch x86_64 /bin/ls reports another 15,095 bytes of __text for the second slice. Four fifths of the file is the structure that tells the loader what to map, what to link, what to trust — plus a __PAGEZERO segment claiming 4 GiB of address space while occupying zero bytes on disk. That structure is what you read when you reverse a binary, and it is what this covers.

The model

A binary is three things: instructions for the CPU, data those instructions use, and metadata telling the loader how to map the first two into memory.

Reversing runs this backwards, and it works because the loader needs the structure present. Names can be stripped and bytes packed, but the entry point, the segment map, and anything resolved externally must survive. That is the floor of truth in every file.

The map

Before any field, the shape of the whole file. All three formats put a header first, a table describing regions second, and the regions themselves third.

PE — a DOS program from 1985 wrapping a modern executable:

┌────────────────────────┐ 0x00
│ DOS header  "MZ"       │──── e_lfanew ────┐
│ DOS stub               │                  │
│ Rich header            │  MSVC toolchain  │
├────────────────────────┤ ←────────────────┘
│ PE signature "PE\0\0"  │
│ COFF header            │  machine, #sections, timestamp
│ Optional header        │  entry point, image base, hardening
│   └ data directories   │──── RVAs to import/export/rsrc/reloc/cert
├────────────────────────┤
│ Section table          │  name, RVA, file offset, size, flags
├────────────────────────┤
│ .text .rdata .data     │
│ .rsrc .reloc           │
├────────────────────────┤
│ certificate table      │  Authenticode
│ overlay                │  anything appended past here
└────────────────────────┘

ELF — two independent tables over one set of bytes:

┌────────────────────────┐ 0x00
│ ELF header (64 bytes)  │──── e_phoff ──┐    e_shoff ────┐
├────────────────────────┤ ←─────────────┘                │
│ Program headers        │  LOAD VIEW — what the loader maps
│   PT_LOAD, PT_DYNAMIC  │                                │
├────────────────────────┤                                │
│ .text .rodata .data    │                                │
│ .bss (no file bytes)   │                                │
│ .dynamic .got .plt     │                                │
├────────────────────────┤ ←──────────────────────────────┘
│ Section headers        │  LINK VIEW — optional at runtime
└────────────────────────┘

Mach-O — a list of commands rather than a table of sections:

┌────────────────────────┐
│ fat header "cafebabe"  │  offsets to each arch slice  (universal only)
├────────────────────────┤
│ Mach-O header          │  cputype, ncmds, sizeofcmds
├────────────────────────┤
│ Load commands          │  LC_SEGMENT_64, LC_MAIN, LC_LOAD_DYLIB,
│                        │  LC_UUID, LC_CODE_SIGNATURE …
├────────────────────────┤
│ __PAGEZERO  (---)      │  4 GiB of nothing, catches null derefs
│ __TEXT      (r-x)      │  __text __cstring __unwind_info
│ __DATA      (rw-)      │  __got __data __bss
├────────────────────────┤
│ __LINKEDIT             │  symbol table, chained fixups, signature
└────────────────────────┘

Each format's header chain, in real bytes.

PE. MZ, a DOS stub, and a pointer at offset 0x3c to where the real header starts:

00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000  MZ..............
00000030: 0000 0000 0000 0000 0000 0000 8000 0000  ................
                                        ^^^^^^^^^ e_lfanew = 0x80

00000080: 5045 0000 4c01 0300 af09 6ba6 0000 0000  PE..L.....k.....
          ^^^^^^^^^ ^^^^ ^^^^ ^^^^^^^^^
          "PE\0\0"  |    |    TimeDateStamp
                    |    NumberOfSections = 3
                    Machine = 0x014c (i386)
00000090: 0000 0000 e000 2200 0b01 3000 0008 0000  ......"...0.....
                    ^^^^ ^^^^ ^^^^
                    |    |    OptionalHeader magic 0x010b = PE32
                    |    Characteristics 0x0022
                    SizeOfOptionalHeader = 0xe0

That TimeDateStamp of 0xa66b09af decodes to the year 2058. It is not corruption — this is a deterministic Roslyn build, where the field is repurposed as part of a content hash. Every build stamp is a claim, and this one isn't even claiming a time.

ELF. A 16-byte identity block, then type, machine, entry point, and the offsets to both tables:

00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
          ^^^^^^^^^ ^^ ^^ ^^
          magic     |  |  version
                    |  endianness: 01 = little
                    class: 02 = 64-bit
00000010: 0200 3e00 0100 0000 8014 0001 0000 0000  ..>.............
          ^^^^ ^^^^           ^^^^^^^^^^^^^^^^^^^
          |    machine 0x3e   e_entry = 0x01001480
          e_type = 2 (EXEC)
00000020: 4000 0000 0000 0000 2017 0000 0000 0000  @....... .......
          ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
          e_phoff = 0x40      e_shoff = 0x1720
00000030: 0000 0000 4000 3800 0b00 4000 1d00 1b00  ....@.8...@.....
                         ^^^^ ^^^^      ^^^^
                         |    11 program headers, 29 sections
                         each 0x38 bytes

Follow e_phoff to 0x40 and the first program header is right there:

00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
          ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
          p_type=6  p_flags=4 p_offset = 0x40
          PT_PHDR   R

Mach-O. cafebabe is not a Mach-O at all — it is an archive of them. This is the same /bin/ls:

00000000: cafe babe 0000 0002 0100 0007 0000 0003  ................
          ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
          fat magic 2 slices  cputype 0x01000007 = x86_64
00000010: 0000 4000 0000 bc00 0000 000e 0100 000c  ..@.............
          ^^^^^^^^^ ^^^^^^^^^           ^^^^^^^^^
          offset    size                cputype 0x0100000c = arm64
00000020: 8000 0002 0001 0000 0001 5c00 0000 000e  ..........\.....
          ^^^^^^^^^
          subtype 0x80000002 — the high bit is PTRAUTH_ABI: arm64e

Each slice is a complete Mach-O at its own offset. Analyzing one says nothing about the other — a fact worth holding onto, because malicious code has shipped in one slice of a universal binary and not the others. Deep analysis runs on one host-preferred slice; the others get an identity-only probe — UUID, signing status, cdhash, team ID, entitlement count — before being split out and re-analyzed in full as separate children. Slices disagreeing on cdhash, team ID or entitlement count are flagged divergent, a repackaging signal no single-slice read can produce.

What every binary contains

Header — magic, target architecture, file type (executable, shared library, object, core), and the entry point. The entry point is not main; it is runtime startup code that eventually calls main.

Segments and sections — sections are the link view: named regions (.text, .data, .rodata) for the linker and debugger. Segments are the load view: coarse ranges with r/w/x permissions the loader maps. The generalization stops there:

ELFPEMach-O
Load viewprogram headersthe section table itselfLC_SEGMENT commands
Link viewsection headerssame tablesections nested in segments
Can the link view be dropped?yes — sstripped ELFs run on program headers aloneno, it is the load viewno, sections live inside segments

So "reason from segments, not sections" is ELF advice. In PE the section table is load-bearing and mandatory, with permissions in per-section IMAGE_SCN_MEM_* bits.

Section names are conventions, not contracts. Nothing stops code living in a section called .data, or a packer inventing .xyz123. Each section carries two sizes — file and memory — and memory-greater-than-file means zero-fill. __PAGEZERO above is that taken to its limit: 4 GiB of memory, zero bytes of file, existing only so that dereferencing a null pointer hits an unmapped page.

Code — machine instructions. The file records only a handful of roots: entry point, exports, and unwind/exception tables. Every other function boundary, the control-flow graph, and calling conventions are recovered by analysis, not read from the file.

Data — initialized (bytes in the file), zero-fill (.bss, no file bytes, which is why image size exceeds file size), and read-only (.rodata: constants, strings, jump tables).

Imports and exports — imports name external functions and the library each lives in; the loader patches an address table at startup (PE: IAT; ELF: GOT/PLT; Mach-O: chained fixups). Exports name what this binary offers others. The import list is a behavioral summary available before reading a single instruction — which is exactly why it gets laundered.

Symbols — names for addresses. Two tiers: the static symbol table (debug leftovers, fully strippable) and the dynamic symbol table (needed for linking, survives stripping). Stripping cannot remove what the loader needs. A statically linked binary (most Go, musl Rust) has no import table at all, so its runtime metadata becomes the naming layer instead.

Relocations — two kinds, routinely conflated. Symbol relocations wire up references to imported symbols. Base relocations patch absolute addresses when the image loads away from its preferred base — these are what make ASLR possible, so a PE with no .reloc cannot be rebased.

Resources, debug info, signatures — embedded structured data; DWARF/PDB/dSYM records, usually shipped separately but often leaving a path or GUID behind; and a cryptographic seal over some subset of bytes.

Hides: alignment padding between sections is real file bytes no section claims — room for a stub, and where a patcher writes a hook. An overlay past the last section is invisible to any tool that walks sections. A section table that disagrees with what the segments map — every sub-parse the parser had to recover from is recorded with its offset and structure (truncated, count_overflow, bad_offset, bad_magic), and a compiler-produced binary yields an empty ledger, so any entry at all is a structural anomaly. That ledger exists for ELF and PE only; Mach-O has none. The inverse of a hiding place is .bss: no file bytes, so nothing can hide there, and a section with no on-disk extent produces no entropy row at all rather than a spurious high reading.

Identity — the hashes

The only question that matters is which bytes the hash covered. Every hash trades "detects any change" against "survives benign rebuilds."

HashCoversSurvivesAnswers
sha256 / sha1 / md5every bytenothing; one flipped bit changes itis this the exact file
per-section sha256one sectionchanges elsewhere in the filewhich part changed
imphash / telfhash / symhashimport or dynamic-symbol names (PE / ELF / Mach-O)rebuilds keeping the same import setfamily kinship
import_md5 (ELF); dylib / import / export / entitlement hash (Mach-O)the named set (needed libraries, bound imports, export trie, entitlement strings), sorted and joinedrebuilds keeping that setnarrower kinship than one blanket symbol hash; byte-exact with yara-x's same-named function, so they pivot into other corpora
hardening hashthe mitigation feature mapthe same build flagsbuild-policy kinship
rich-header hash (PE)the MSVC build-tool fingerprintthe same build environmenttoolchain kinship
ssdeep / TLSHrolling window over contentedits of a few percentnear-duplicate pivots; the keys other corpora index on
function / quad hashesper-function code, normalizedrelinking, rebasing, address churn — not different codegenshared code across different files
DNA vector106 structural featuresrecompiles, superficial changebehavioral similarity, not byte similarity

Build IDs tie a stripped shipped binary back to its debug artifacts:

Everything in the version-info block — original filename, product name, company, version — is typed by whoever built the file. A dropper claiming to be v1.0.0.0 of a trusted product costs nothing.

Hides: flip one byte and every whole-file hash misses, which is the entire reason the other rows exist. A leaked PDB path hands over the build machine, the project name, and the author's directory layout — the .NET sample hexdumped above carries C:\Users\pzixe\Documents\Repos\test\obj\Debug\netcoreapp2.1\test.pdb, down to the account name and the fact that a Debug build shipped.

PE

The three impossible TimeDateStamp readings, each with a measured margin:

ReadingFires whenWhy that margin
Signed before builtthe stamp postdates a TSA-attested Authenticode signing time by more than 24 hourscausally impossible; needs a cryptographically trusted timestamp, not merely a signature
Futuremore than 2 years past the analysis yeara build farm with a clock skewed across a New Year cannot fire it
Impossibly oldnonzero and below 1992-01-01the PE format's own floor

Zero is not scored at all: GNU ld/MinGW default the field to 0, most packers zero it, and .NET assemblies routinely carry 0. A deterministic build does not zero it either — it writes a content hash, which is why the 0xa66b09af above decodes to 2058. Reproducible-build, platform-signed and managed binaries are excluded outright.

Build hardening:

FlagOn meansOff costs you
aslr (DYNAMICBASE)image loads at a randomized basefixed addresses make exploits portable
nx_compat (DEP)data pages non-executablecode runs from the stack or heap
cfgindirect-call targets validatedcall sites hijackable
gs_cookiestack canary presentstack overflows go undetected
safesehexception handlers validated (x86)SEH-overwrite exploitation
cet_compatCET: shadow stack + indirect-branch trackingROP and return-address tampering
force_integritysignature checked at loadmodified code loads anyway
high_entropy_va64-bit ASLR entropyweaker randomization
authenticode_signedan embedded signature is presentnothing seals the bytes

Two of those nine rows do not mean what the headings promise:

RowWhat it actually readsSo the value means
safesehnothing — it is hard-wired to n/a on every PE; the handler table's address and count are parsed out of the load-config directory and then go unusednever a real 32-bit image's SEH posture
gs_cookiethe names __security_cookie / _security_cookie, in the import table and export directory — a lookup, not a probeOff = "no exported cookie symbol", not "no canary". Statically-linked CRTs carry neither name, so AnyDesk.exe, curl and jq for Windows all read Off and each raises a "no stack cookie" finding, with /GS on by default in all three

Elsewhere n/a means the flag was not evaluated rather than off: high_entropy_va applies to x86_64 only, so a 32-bit or arm64 PE reads n/a.

Hides: an overlay past the last section. The certificate table legitimately lives out there too, and the two readers disagree — the header-level overlay detector excludes the certificate range, the one feeding the entropy row and the packing signal does not, so a signed PE reports its Authenticode blob as overlay bytes at compressed-blob entropy. Imports by ordinal (a number, no name to read) or delay-loaded — a separate table, absent from the main import list. That directory is parsed and then read by nothing, so a delay-loaded name reaches no rule, no hash and no capability — the analyzed import surface is the import directory plus .NET P/Invoke only. Code caves in alignment slack, which no section walker shows — though caving a signed PE produces exactly the digest mismatch above, because slack falls inside the Authenticode digest range.

ELF

Hides: a PT_LOAD mapped rwx — writable and executable is a self-modifying or unpacking tell. An RPATH/RUNPATH pointing at an attacker-writable directory is a library-search hijack baked into the header. Data after the last section header.

Mach-O

Hides: ad-hoc or absent signatures; over-broad entitlements (a "calculator" requesting com.apple.security.cs.allow-jit); malicious code in one slice of a fat binary and not the others.

Language runtimes

Obfuscation and evasion

Packing and entropy. Compiled code lands around 4.5–6.5 bits per byte on a 0–8 scale.

ReadingWhereMeans
above 6.8a code sectionflagged high-entropy
above 7.5a data sectionflagged high-entropy
above 7.7any regioncompressed or encrypted rather than code
under 256 bytesany regionreports 0.0 and is never flagged — a 256-bucket histogram over fewer than 256 samples is noise, so a sub-256-byte encrypted config is invisible here by construction

Two conditions in practice mark a binary packed: under 30 imports together with code entropy above 6.8, or maximum entropy anywhere in the file above 7.7 while some code section is already flagged at 6.8. That maximum is taken across every section and the synthetic overlay row, so the second trigger does not need a hot code section — a .text at 6.9 with a 7.9 overlay fires it with nothing above 7.7 in any code section at all. Packing is never a verdict on its own; compressed installers are legitimate.

Two consequences. The interesting band is 6.8–7.5, so a "look for 7.9" reflex misses where packer stubs and encrypted configs live. And entropy is defeatable downward — base64 or single-byte XOR parks a payload at 4–6 bits per byte and raises nothing. High entropy hides content while being conspicuous, so the careful adversary avoids it.

Section names are a claim. UPX0/UPX1 are strings a packer wrote. Malware routinely zeroes the UPX! magic and renames sections so upx -d and signature scanners fail while the packed layout still runs. The layout is recognizable from the program headers regardless, in two bands: markers intact is an identity row that carries points but never elevates a tier on its own, because packing is dual-use; both surface markers gone while a UPX loader-stub remnant survives is the evasion tell, because the defacement — not the packing — is what was chosen. That two-band reading is ELF only, so a mangled UPX PE gets nothing, and the native unpacker still anchors on the UPX! magic, so a defaced sample is labelled but never unpacked — its payload stays unread.

Same trick a layer up: overwriting four bytes of Go's pclntab magic makes every tool report a Go binary as stripped, while the runtime, which reaches the table through moduledata, never notices. Measured: 33 of 115 corpus Go executables patched, 0 of 23 benign.

API hashing — the import table's endgame. Rather than naming VirtualAlloc, the code walks the export table hashing every name (ror13, djb2, FNV) and calls whichever matches a constant. Zero imports, zero strings, one indirect call. The first artifact is the shape: a tight bitwise-dense loop with a single back edge that loads bytes and ends in an indirect call, inside a function making almost no direct API calls. The constant is also invertible: emulating the recovered recurrence over a bundled export-name catalog builds a reverse hash → name map, so the constant resolves back to VirtualAlloc and the indirect call becomes a named call-graph edge with recorded provenance. That catalog is a hand-seeded kernel32-only first cut, so a resolver targeting ntdll or ws2_32 names resolves nothing, and a hash colliding onto two or more names resolves to neither, by policy.

Stack strings — a string that never exists in the file. Code writes it onto its own stack frame, so strings returns nothing. Reconstruction from the IL puts it on the same matching surface as real strings, so a rule keyed on kernel32.dll fires on one assembled by mov qword [rsp], 0x…. That surface is a three-way union — ordinary strings, reconstructed stack strings, strings recovered by a decoder — and each hit carries its own evidence class, so a decoded match reports itself as recovered rather than as a literal in the file. Store width decides whether the pass produces anything at all: reading only 1-byte stores recovered one stack string across 679 corpus samples and 100,639 functions, because 1-byte stores are not the idiom real x86-64 and Mach-O code uses; 1/2/4/8-byte stores are what make it work. Benign jq and curl build stack strings too, so the discriminator is whether the run's address is ever taken — passed to a callee, stored, dereferenced. A run nothing points at is a numeric constant that happens to look like text.

String encryption. Everything interesting is XOR'd or base64'd in read-only data and decoded on first use. Two ways in: recover the plaintext by brute-forcing single-byte XOR and base64 against anchors — tokens an obfuscator would plausibly be hiding — or detect the decoder, since a tight, flat, bitwise-dense loop making almost no API calls is a hand-rolled deobfuscator, and having one is intent. The anchor search costs one pass for all 255 keys: what it scans for is the delta signature token[i] ^ token[i+1], which single-byte XOR leaves unchanged. Eight anchors (http://, .onion, https://, User-Agent, Mozilla/5.0, Content-Type, and two macOS support paths) produce 311 hits across 32 of 1,416 malware samples and 0 across 121 benign files. Anchor choice is the whole game — kernel32.dll, CreateProcess, VirtualAlloc, powershell, cmd.exe and a dozen other obvious guesses measured at zero yield and were dropped.

Anti-debug and anti-VM. The cheap primitives are visible statically: PTRACE_TRACEME or ptrace(PT_DENY_ATTACH) self-tracing, TracerPid parsed from /proc/self/status, ThreadHideFromDebugger, CheckRemoteDebuggerPresent, PEB BeingDebugged and NtGlobalFlag reads, hardware-breakpoint register checks. Anti-VM is louder: a binary carrying the exact 12-byte CPUID hypervisor brand (VMwareVMware, KVMKVMKVM) is comparing against it, and scanning for analysis-tool or security-product process names is unambiguous. What is not a tell: IsDebuggerPresent and OutputDebugStringA in the import table are MSVC CRT boilerplate — benign jq for Windows imports both.

Direct syscalls. Skip ntdll and issue syscall with the service number inline, so user-mode EDR hooks never see the call. Statically this surfaces as the toolkit that generated it — SysWhispers, Hell's/Halo's/Tartarus Gate, FreshyCalls — rather than as an import.

Injection. Visible without running anything: VirtualAllocEx + WriteProcessMemory + CreateRemoteThread co-occurring, or the constant 0x40 (PAGE_EXECUTE_READWRITE) resolved into an allocation call in one function. On macOS DYLD_INSERT_LIBRARIES; on Linux an LD_PRELOAD= assignment. Require the co-occurrence — each API alone is dual-use with JIT and managed runtimes.

Timestomping. Beyond the header field, SetFileTime and friends backdate dropped files. This one demands discipline in the other direction: SetFileTime in an import table is a fact, not a technique, because curl imports it to implement --remote-time.

Obfuscated names. garble rewrites Go symbols to random identifiers (internal/bytealg/Se1IQnZ0I44f); ConfuserEx, Dotfuscator and friends do the same to .NET metadata. The names are gone but the shape is loud. On Go it is measurable: 34 of 114 corpus Go images recover only 13–24 stdlib package names, against the 30 packages present in at least 95% of Go binaries and the 259 distinct stdlib packages seen across the corpus — a full runtime with a near-empty package list. On .NET the tell is the protector's own signature rather than a name-shape metric: ConfusedByAttribute, DotfuscatorAttribute, SmartAssembly.Attributes.PoweredByAttribute, EazfuscatorAttribute and BabelObfuscatorAttribute are marker attributes stamped into the assembly, each naming its product outright. A protector that strips its own marker is not identified.

Control-flow flattening and opaque predicates. The CFG is replaced by a dispatch loop over a state variable, or branches are inserted whose outcome is constant but not provably so. Behavior is unchanged; decompilation becomes unreadable. The manual tell is one enormous switch over a state variable and a basic-block count out of all proportion to what the function does. An opaque predicate is proved, not merely noticed: if the flow-sensitive value-range fixpoint shows the compared variable's range on entry to the block already lies entirely inside the range the true edge would impose, the branch cannot go the other way, so it is rewritten to an unconditional jump and the dead arm dropped — which exposes the junk behind it to a dead-code sweep. A dispatcher whose jump table is built at runtime, on the stack or heap, is out of reach of a read-only-image table reader; value-set analysis supplies the target set instead and splices the real edges into the CFG, behind soundness gates and tagged with its origin so a VSA-derived edge is never mistaken for one read from the file.

Absence is a signal. A 400 KB Mach-O with an empty __cstring, forty imports, and posix_spawn on the list is not a small program — it is a program that reconstructs everything at runtime and delegates the work to a shell. Reading structure instead of content is immune to any string cipher, which is why that one shape separates 106 of 119 AmosStealer samples from 0 of 1,424 benign Apple binaries.

Claims versus measurements

Every field is one or the other, and conflating them is the standard analytical error.

gs_cookie looks like a measurement and is not: it reads a name out of the import or export table, and names are claims. FORTIFY on ELF has the same character. A probe that reads names inherits the trustworthiness of names.

Measurements can still be engineered against — entropy most of all, which is why it is paired with structural eligibility rather than trusted alone. But the gap between a claim and a measurement is where the finding lives: a section named .text with data entropy, a signature present with a mismatched digest, a benign product name over process-injection primitives.

Self-check

  1. Segments versus sections — and why is "the section table is optional" true only for ELF?
  2. /bin/ls is 154,624 bytes and 30,363 of them are instructions, across two slices. What is the rest?
  3. What is __PAGEZERO for, and how can it occupy 4 GiB while adding nothing to the file?
  4. Why can imphash match when sha256 differs completely?
  5. An icon resource and a version-info block both read entropy 7.9. Which is suspicious — and what does a .rsrc leaf that sniffs as pe establish that entropy alone does not?
  6. Why can nothing hide in .bss, and what does a section with no on-disk bytes report for entropy?
  7. What can stripping never remove — and what does a statically linked binary not have at all?
  8. high_entropy_va shows n/a. What does that say about the architecture?
  9. What does a leaked PDB path give you?
  10. Why do Go function names survive stripping, and which four bytes does an author patch to break that?
  11. A PE imports only LoadLibrary and GetProcAddress. What does that suggest, and why is it not a verdict?
  12. Absent, digest-match, digest-mismatch, unverifiable — which is not a tamper claim?
  13. Where does an overlay live, and what legitimately lives out there too?
  14. What do entitlements tell you without running the binary?
  15. What are #~, #US, and #Blob?
  16. Why is the number of TLS callbacks not a signal, and what would be?
  17. RELRO Full versus Partial — what is the difference?
  18. High entropy is conspicuous. What does a careful adversary use instead, and what happens to the number?
  19. What two conditions actually mark a binary as packed — and why can the second fire with no code section above 7.7?
  20. A binary has almost no imports and almost no strings. What single technique explains both, and what has to be bundled before the hidden name can be recovered rather than merely suspected?
  21. What is a stack string, and why does strings never show one?
  22. Why is SetFileTime in an import table a fact rather than a technique?
  23. Mach-O UUID versus cdhash — which names the build, which seals the bytes?
  24. Why can a fully static ELF show every hardening probe "off" and still be hardened?
  25. cafebabe — what have you actually got, and what has analyzing it told you about the rest of the file?

Reference