Binary inspector: Mach-O, ELF and PE
Drop an executable or library — Mach-O (thin or universal), ELF or Windows PE — and see its load commands or headers, libraries and run paths, symbols demangled, fixups, the code signature with entitlements and requirements, a checksec-style hardening list, Go build info, Rust crates, strings, a hex view of any section and a size treemap.
Added
Works with
- Dependency tree analyser Formatting and referenceOpen in dependency tree hands it the Go modules (as go.mod and go.sum) or Rust crates (as a Cargo.lock) found in the binary
How to use it
The questions I ask of a binary are always the same: what is it built for, what does it link
against, is it signed and how, what’s in it, and why is it so big. On a Mac that’s otool -l -L,
codesign -dvvv, vtool and dyld_info; on Linux readelf, objdump and checksec; for Go
go version -m. This page answers all of them for one file at once, in the browser. It opens on a
small universal C program; the Examples menu has Go, Rust, C++ and Windows ones I built for it.
How to use it
Drop a file on the box, click it to pick one, or drop an .app folder (or use Open .app
folder…): the main executable is opened, and the bundle’s Info.plist and stapled ticket are
checked. Gzipped files are unpacked first. For a universal binary, pick the architecture at the
top; every tab follows it.
-
Overview — format, CPU, platform and minimum OS (from
LC_BUILD_VERSION), UUID or build ID, entry point, the language it was written in (from what the other tabs find), and a card per topic below. -
Load commands (Mach-O) — every command with what it says, and the header flags explained (
PIE,TWOLEVEL,NO_HEAP_EXECUTION…). Headers (ELF, PE) — program headers, dynamic section, notes (build ID, ABI tag, GNU properties), GNU version needs — with the newest glibc version it needs — or the PE data directories and exports. -
Sections — per segment, with addresses, sizes and types. Click one to see its bytes.
-
Libraries —
LC_LOAD_DYLIB, weak, re-exported and upward dylibs with their compatibility and current versions,LC_RPATHentries and where@rpath/names would be looked for; or ELF’sNEEDED,RPATH,RUNPATH,SONAMEand interpreter; or the DLLs a PE imports and from each, which functions. -
Symbols — exported, imported and local symbols with the library each import comes from (two-level namespace on macOS, GNU symbol versions on Linux), searchable by text or
/regex/, with C and Rust names demangled. C follows LLVM’sc++filt(>>rather than GNU’s> >); Rust handles both the legacy_ZN…17h…Escheme and v0 (_R…) asrustfiltprints it. -
Fixups (Mach-O) — chained fixups (macOS 12 and later): each segment’s pointer format and how many binds and rebases it holds, the import table, and every bind with its target, or the classic
LC_DYLD_INFObind opcodes for older binaries. The same listdyld_info -fixupsprints. -
Signature (Mach-O) — who signed it (ad hoc, Developer ID, Apple, development, App Store) and what that means; a checklist of what notarization needs (Developer ID, hardened runtime, secure timestamp, no
get-task-allow); the CodeDirectory ascodesign -dvvvprints it, with the cdhash; the flags explained; entitlements from both the XML and the DER blob (and a warning if they differ); the requirements decompiled to text (designated ⇒ identifier "…" and anchor apple generic and …); the certificate chain from the CMS signature; and Verify page hashes, which hashes every page of the code and compares it with the signature. -
Hardening (ELF, PE) — the checks
checksecmakes: RELRO, stack canary, NX, PIE, FORTIFY_SOURCE (which calls were fortified and which could have been), RPATH and RUNPATH, plus Intel CET (IBT, shadow stack) or Arm BTI and PAC from the GNU property note, text relocations and stripping. For PE: ASLR, high-entropy ASLR, DEP, Control Flow Guard and whether it carries an Authenticode signature. -
Go — the build info every Go binary since 1.18 carries: Go version, main package and module, every dependency module with its version,
go.sumhash and replacement, and the build settings (-trimpath,CGO_ENABLED,GOARCH,-ldflags,vcs.revision,vcs.modified), also as the exact textgo version -mprints. -
Rust — the compiler version from
.comment(ELF) or the commit in/rustc/<hash>/paths; the crate list from cargo-auditable’s.dep-v0section if the binary was built withcargo auditable build; and, even in a stripped binary, every crate and version that appears in a panic location’s registry path. -
Strings — printable strings by section, with a minimum length, a text or regex filter, and quick filters for paths, URLs, format strings, versions and environment-style names.
-
Hex — any section or the whole file, a page at a time, jump to an offset.
-
Sizes — a treemap of the file by segment and section, with
__LINKEDITsplit into its symbol table, strings, fixups, exports and code signature, and a table of the largest parts.
Open in dependency tree ↗ on the Go and Rust cards sends the modules or crates to the
dependency tree analyser: Go modules as a go.mod and go.sum, Rust crates
as a Cargo.lock (with the dependency edges when cargo-auditable recorded them), where you can
check them against OSV.dev for known vulnerabilities.
For Java bytecode use the class file and JAR inspector; for how a struct is laid out in memory, memory layout; for protobuf, MessagePack and other data formats, binary formats.
Notarization and stapled tickets
Notarization happens at Apple, so a page can only tell you whether a binary is ready for it and
whether a ticket is stapled. A ticket can be stapled to an .app, .dmg or .pkg, never to a
bare Mach-O file: for a lone command-line tool Gatekeeper looks the ticket up online the first time
it runs. Open the .app folder to see whether Contents/CodeResources holds a stapled ticket.
How I checked it
I compiled small programs for the examples — C as a universal arm64 and x86_64 binary, ad-hoc
signed with entitlements; Go 1.23 and Rust 1.99 (with cargo-auditable) for macOS and Linux; C and
C for Linux with and without hardening; a shared library; and a Windows program with MinGW — and
the tests compare this page with what the real tools say about them: `otool -l -L`, `vtool`,
`dyld_info -fixups -exports`, `codesign -dvvv --entitlements - -r-`, `csreq -t`, `go version -m`,
`rust-audit-info`, `readelf`, `nm -DC`, `checksec` and `objdump -p` (the Linux ones in a Debian
container). I also checked the signature parsing against signed apps on my Mac (Chrome, Slack and
Rancher Desktop with Developer ID or App Store signatures and secure timestamps, and Apple's own
`/bin/ls`), and the demangler against `cfilt` over 160,000 symbols from LLVM’s libraries (97–99%
identical; the rest are rare template-expression forms) and against rustfilt.
The Go examples have their machine code and read-only data zeroed to keep them small, so Verify page hashes reports changed pages on the macOS one. That’s the check working.
Not covered: Swift metadata beyond class names, DWARF debug info, PE resources and Authenticode
certificate details, static libraries (.a), and kernel or firmware file sets.
Browser support
Any current browser. Gzipped files are unpacked with DecompressionStream, and cdhashes and page
hashes use the Web Crypto API (both in Chrome, Edge, Firefox and Safari 16.4+). Files over 64 MB
are read a megabyte at a time with Blob.slice, so only the parts a view needs are loaded.
Dropping an .app folder works in Chrome, Edge, Firefox and Safari; Open .app folder… uses the
directory picker.
Privacy
Everything happens in this page: files and folders you open are read by your browser and never uploaded. Open in dependency tree writes the module list into this browser’s local storage for that page to read; the dependency tree page only contacts OSV.dev if you press its vulnerability button. The share link carries the chosen example and tab, not your file.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.