Reading LC_CODE_SIGNATURE with 0tH
Introduction
0tH has been available for download for two weeks now, and in the meantime I’ve rewritten — and significantly improved — the LC_CODE_SIGNATURE parser.
In this post I’ll show you how to use the upcoming release (0tH2026.2.0, available on December 22nd) to analyse the code-signature blob of any Mach-O. Consider this a short tutorial with a few sneak-peek features scattered around. A small gift for those of you who actually care about what’s under the hood.
Test plan
We’ll inspect three binaries, each sitting at a different point on the macOS security ladder:
- A trivial “do-nothing” app — unsigned and non-notarised. The absolute bottom of the security spectrum.
- 0tH version 2026.1.0 — fully signed and notarised, but without some of the newer SignatureBlob niceties.
- Safari — Apple-signed, sealed, and sitting at the top of the trust chain.
The idea is to cover the full span: from untrusted junk to first-party, deeply-entangled, Apple-grade binaries.
ASM_o_DETH — Ad-Hoc Baseline
ASM_o_DETH is the ideal starting point: a minimal Mach-O with no entitlements, no hardened runtime, no notarisation, and a simple ad-hoc signature automatically emitted by the Apple toolchain.
Load Commands
The binary contains 17 load commands, with an LC_CODE_SIGNATURE at the end.
Its presence does not imply a real signature — only that a placeholder container exists.
LC_CODE_SIGNATURE
cmdview 16 shows:
- Data offset: 0x8100
- Data size: 408 bytes
The signature container is a minimal SuperBlob:
- Magic:
0xFADE0CC0 - Length: 401 bytes
- Total blobs: 1
The only entry is a CodeDirectory.
CodeDirectory
codesign show outputs:
- Identifier:
main - Version:
0x00020400- Hash type:
SHA-256
- Hash type:
- Flags:
0x00020002(adhoc, linker-signed) - Hash slots:
0 + 9(special + code) - CDHash:
b1451656bf9aecf20d2950ad00b0b4d02926a22d204b1b3ff34025266b32d721
This is the binary’s complete identity: a bare CodeDirectory with no certificates and no CMS blob.
Structural Verification
codesign verify confirms:
- 1 SuperBlob
- 1 CodeDirectory
- No developer certificate
- Hardened Runtime disabled
Result: structure is valid (As expected for an ad-hoc signature.)
Page Hashes
codesign hashes displays the 9 code pages (4096 bytes each):
- Pages 0–8 → valid SHA-256 hashes
- Repeated values across multiple pages (normal for very small binaries)
- Code limit: 33024 bytes
Everything aligns with the size and alignment of ASM_o_DETH.
Entitlements
codesign entitlements returns None. Just as planned.
Info Summary
codesign info reiterates:
- Identifier: main
- Platform: macOS
- Hash algorithm: SHA-256
- Hardened Runtime: OFF
- Total pages: 9
All consistent with an ad-hoc signature.
Requirements
codesign requirements gives No requirements found.
Certificates
codesign certificates shows there is no CMS signature, no certificate chain.
Standard for ad-hoc binaries.
Notarization
codesign notarization → No notarization ticket. Exactly what we expect.
Conclusion
ASM_o_DETH provides the cleanest possible baseline: a single CodeDirectory, linker-signed, with no additional security structures.
This is level zero of the code-signing hierarchy.
Next, we’ll move upward toward fully signed and notarised binaries.
0tH - recursively bad mannered
Introducing real verbosity levels
One of the most visible changes in 0tH2026.2.0 is something that was simply not there before: actual verbosity levels.
Until this release, 0tH printed a single, fixed amount of information: the “standard” level.
Useful, yes — but limiting whenever you needed either:
- a greppable, one-line-per-event output for scripting, or
- a fully expanded, introspection-friendly view while reversing.
For this analysis we enable the most talkative setting:
>> set verbosity verbose
Right after enabling it, we load 0tH itself:
>> load binaries_to_test/0tH
And we immediately get a rich summary:
- FAT binary with 2 slices
- 17 load commands on x86-64
- 18 load commands on ARM64
- explicit slice ranges
- explicit command counts
- architecture-specific parsing confirmation
If ASM_o_DETH is the bare ad-hoc baseline, 0tH sits several steps above it:
a FAT binary, fully signed with a CMS-backed signature, hardened runtime enabled, explicit requirements, a full certificate chain, and an actual notarisation ticket stapled to the binary.
All of this is visible directly from LC_CODE_SIGNATURE.
LC_CODE_SIGNATURE (ARM64 slice)
We focus on the ARM64 slice (slice 1):
Slice number: 1
Architecture: ARM64 (ARMv8)
Load Command: LC_CODE_SIGNATURE
Data Offset: 0x3E8F40 (4099904 bytes from file start)
Data Size: 26416 bytes
The signature data occupies ~26 KB at the end of the file.
The embedded SuperBlob looks like this:
Code Signature SuperBlob
Magic: 0xFADE0CC0
Length: 17395 bytes
Blob Count: 3
Signature Characteristics
Type: Full Signature with CMS
CodeDirectory: present
Requirements: present
Blob Slots
CodeDirectory: offset 0x24
Requirements: offset 0x2033
CMS Signature: offset 0x20D7
Compared to ASM_o_DETH’s single CodeDirectory, 0tH now carries:
- a CodeDirectory,
- a Requirements blob,
- a CMS signature blob.
This is a full, first-class macOS code signature.
SuperBlob structure
codesign show confirms the internal layout: the ~17 KB SuperBlob is split into:
- ~8 KB of CodeDirectory,
- 164 bytes of Requirements,
- ~9 KB of CMS data.
CodeDirectory
codesign showandcodesign infogive us the full CodeDirectory metadata:
Identifier: 0tH
Version: 0x00020500
Hash Type: SHA-256
Flags: 0x00010000 (runtime)
Hashes: 2+251 (special+code)
CDHash: 584cf09dfe56bf06f15ca4f099cc2bf826947aeacbe694fe9eb71d8dcf6f035a
and
Team ID: 693DSH8GN5
Platform: macOS
Page Size: 16384 bytes
Total Pages: 251
Code Limit: 4099904 bytes
Flags:
• Hardened Runtime enabled ✔
Runtime Version:
26.256
Key points:
- The binary is identified as 0tH, signed under Team ID 693DSH8GN5.
- It uses SHA-256 with 251 code pages of 16 KB each.
- Hardened Runtime is enabled, which is essential for modern macOS tooling.
- The CDHash is the canonical identity of the signed binary.
Structural verification
codesign verify summarises the signature:
Code Signature Verification
SuperBlob: 3 blobs
CodeDirectory: 0tH
CMS
HardenedRuntime: Enabled
Result: Signature structure is valid
Note: This is structural validation only. Cryptographic verification requires system keychain.
So:
- The structure is fully valid.
- Hardened runtime is correctly detected as enabled.
- The presence of CMS is recognised, even though cryptographic verification is delegated to the system keychain.
Hashes
codesign hashesnow shows a much larger footprint than ASM_o_DETH:
Identifier: 0tH
Hash Type: SHA-256
Page Size: 16384 bytes
Code Limit: 4099904 bytes
Special Slots (1):
Slot -2: Requirements → e9f8…
Code Page Hashes (251 pages):
#0..250 → SHA-256 hashes
Notable differences vs ASM_o_DETH:
- Page size jumps from 4 KB to 16 KB.
- A special slot exists for Requirements.
- Instead of 9 pages, we now have 251 pages, covering the entire 4 MB+ code region.
This is what a real-world, non-trivial binary looks like under Apple’s signing model.
Entitlements
0tH does not use entitlements. The hardened runtime is enabled, but there are no extra sandbox or capability declarations baked into the signature.
Requirements
The Requirements blob holds a single designated requirement:
Total Requirements: 1
Requirement Type: Designated
Expression:
(identifier "0tH" and (anchor apple generic and (certificate [1] <10
bytes> and never)))
Security Warnings:
• Designated requirement is contradiction (always false)
So the designated requirement expression combines:
- the identifier 0tH,
- a generic Apple anchor,
- and a never clause that makes the expression contradictory.
The tool correctly flags this as “always false”. It’s still a valid requirements blob, but semantically self-contradictory.
Certificates
codesign certificates revealed the full chain. So 0tH is signed as:
- Developer ID Application: Gabriele Biondo (693DSH8GN5),
- under the Developer ID Certification Authority,
- ultimately rooted in Apple Root CA.
This is the standard, expected chain for a Developer ID–signed macOS tool.
Notarization
Finally, codesign notarization confirms that 0tH is notarized:
Notarization Status
Notarization Ticket Present
Ticket Offset: 0x000020D7
This binary has been notarized by Apple.
Summary: from ad-hoc to fully trusted
Compared to ASM_o_DETH’s minimal ad-hoc CodeDirectory, 0tH shows the full weight of the modern macOS trust stack:
- FAT binary, with separate x86-64 and ARM64 slices.
LC_CODE_SIGNATUREthat holds:- a non-trivial CodeDirectory,
- explicit Requirements,
- a full CMS signature.
- Hardened Runtime enabled.
- Developer ID Application certificate chain (Apple-rooted).
- Notarization ticket present.
In other words: 0tH is a real citizen of the macOS security ecosystem — not just a toy binary. And 0tH’s own codesign commands let you prove that, down to the last page hash.
Safari - the great-old-one
Safari is not interesting because of its structure — it is interesting because 0tH handles it without flinching.
What matters here is not “how Safari works,” but what changes when you move from a simple developer binary to a first-party Apple application.
1. Full SuperBlob (5 blobs)
Safari includes every major blob type:
- CodeDirectory
- Requirements
- Entitlements
- DER Entitlements
- CMS Signature
This is the complete Apple signing layout, larger and more articulated than 0tH’s 3-blob structure.
2. Entitlements: the real payload
Safari ships with 155 entitlements, including privacy-sensitive ones (camera, microphone, contacts, calendars, location, USB), plus dozens of private system-only capabilities (WebKit, TCC, CoreServices, Accounts, CloudKit, RemoteServiceDiscovery, Parsec, etc.).
This is the main difference vs. 0tH: Safari doesn’t rely on its code — it relies on its entitlements.
3. Minimal requirements
Only one designated requirement:
(identifier "com.apple.Safari" and anchor apple)
Clean, standard, nothing exotic.
4. Hardened Runtime surprisingly off
Safari’s loader binary shows:
- HardenedRuntime: NOT enabled
- LibraryValidation: required
Exactly what you expect from a system binary that needs to load internal or legacy components.
5. CMS chain fully intact
Three certificates:
- Apple Code Signing CA (intermediate)
- Software Signing (leaf)
- Apple Root CA
The chain is valid and complete.
6. Tiny executable footprint
Only 3 code pages — the actual loader is extremely small. Everything else lives in frameworks.
How to Analyse LC_CODE_SIGNATURE — The General Method
When you strip away the noise, analysing a Mach-O code signature always follows the same four steps. Tooling helps, but the mental model is the real skill.
1. Locate the signature
Find LC_CODE_SIGNATURE:
- It lives in the
__LINKEDITregion. - It stores offset and size, not the signature itself.
- If the command is missing: the binary is unsigned (or someone removed the signature).
This is the entry point: without these two numbers, there is no signature.
2. Parse the SuperBlob
Every signature begins with:
0xFADE0CC0 (CSMAGIC_EMBEDDED_SIGNATURE)
This is the SuperBlob, a container with N “slots”. Each slot has:
- an offset
- a type
- a size
The slots you expect in a normal, hardened, Apple-signed binary:
- CodeDirectory
- Requirements
- Entitlements
- DER Entitlements (modern hardened pipeline)
- CMS Signature (the PKCS#7 payload)
If something is missing (or appears malformed), you already have your anomaly.
3. Analyse the CodeDirectory
This is the heart of the signature.
The CodeDirectory tells you:
- the identifier (
com.apple.Safari) - the hash algorithm (SHA-1 for relics, SHA-256 modern)
- the CDHash (the binary’s identity)
- the page size
- the number of code pages
- flags (hardened runtime, library validation, etc.)
This is the part most tooling hides. If you don’t understand CodeDirectory, you don’t understand macOS code signing.
4. Inspect “special slots”
Special slots are hashes for things that are not code pages:
|Slot|Meaning| |—|—| |-1|Info.plist| |-2|Requirements| |-3|Resource Directory| |-5|Entitlements| |-7|DER Entitlements| If any of these hashes do not match the payload, the signature is broken.
This is exactly where tampering becomes visible.
5. Look at entitlements (the real power)
Entitlements define capabilities. In practice, this is where you see:
- access to contacts, mic, camera
- sandbox exceptions
- private SPI usage
- internal-only capabilities
- Apple-exclusive functionality
- hidden flags (e.g., hardened-process, restricted-application-groups)
This is the part that tells you what the binary really is, not what it claims to be.
Safari having 155 entitlements? Just as planned!
A third-party app with 40+ internal ones? Huge red flag.
6. Requirements (the policy)
Most binaries have a minimal requirement:
identifier "com.example.app" and anchor apple
If you see custom requirement expressions, that’s noteworthy.
Requirements express trust policy. They rarely change, so anomalies are easy to spot.
7. Certificates and chain
A valid chain should:
- end at Apple Root CA
- include the Apple Code Signing CA
- include the leaf signing certificate
Expired, mismatched, or unusual chains are reliable indicators of repackaging.
8. Notarization
Presence of the notarization ticket proves:
- the binary passed Apple’s automated scanning
- Gatekeeper will not quarantine it
- it fits the modern distribution pipeline
Unsigned and not notarized doesn’t necessarily mean malicious, but: suspicious, or amateurish.
However, this post would be incomplete if we didn’t show how the very same analysis can be performed using the canonical macOS tooling. After all, 0tH is my way of doing it — but if you stick to the system tools, the workflow looks very different, and far more fragmented. Here is the equivalent journey with Apple’s own utilities.
1. Basic Signature & Identity Info – codesign
First pass: let codesign tell you what it sees.
codesign -d --verbose=4 /Applications/Safari.app/Contents/MacOS/Safari
From this you can extract:
- The identifier (com.apple.Safari)
- The CDHash
- The team identifier
- The signature flags (hardened runtime, library validation, etc.)
- High-level info about the signature status
This is roughly equivalent to the summary you get from:
>> codesign info
>> codesign verify
inside 0tH. What you do not get here are:
- The exact
LC_CODE_SIGNATUREoffsets and sizes - The SuperBlob layout (slots, types, lengths)
- The per-page hashes
2. Locating LC_CODE_SIGNATURE – otool
To get anywhere close to the structural view of 0tH, you need to drop down to
otool:
otool -l /Applications/Safari.app/Contents/MacOS/Safari | sed -n '/LC_CODE_SIGNATURE/,/EndOfCommand/p'
(Replace EndOfCommand with a pattern like cmdsize / the next cmd header and trim as you prefer.)
From this block you can manually recover:
- The
LC_CODE_SIGNATUREcommand - The
dataoff(file offset of the SuperBlob) - The
datasize(total size of the SuperBlob)
This is the canonical way to approximate what 0tH shows in one shot with:
>> cmdview 1, 19
which directly prints absolute and relative ranges, offsets and sizes.
3. Extracting and Inspecting the SuperBlob
Once you know dataoff and datasize, you can rip out the SuperBlob manually:
dd if=/Applications/Safari.app/Contents/MacOS/Safari \
of=/tmp/safari.codesig \
bs=1 skip=<dataoff> count=<datasize>
At this point you have a raw SuperBlob on disk, so you can hexdump it:
hexdump -Cv /tmp/safari.codesig | head
…but you still do not get a parsed view like:
>> codesign show
LC_CODE_SIGNATURE - SuperBlob Structure
Total Blobs: 5
...
With canonical tools, understanding the SuperBlob structure means manually decoding the Code Signing format (magic, count, offsets, etc.) or writing your own parser.
That parser is, essentially, what 0tH embeds.
4. Entitlements – codesign
Entitlements are the one part Apple makes relatively convenient:
codesign -d --entitlements :- \
/Applications/Safari.app/Contents/MacOS/Safari
This prints the XML entitlements plist to stdout. This corresponds to what 0tH shows with:
>> codesign entitlements
with two key differences:
- codesign shows the plist, but does not highlight “notable” entitlements for you.
- codesign does not cross-reference the entitlements blob with its hash slot in the CodeDirectory – you have to trust that they match.
5. Requirements – codesign
Same story for requirements:
codesign -d --requirements :- \
/Applications/Safari.app/Contents/MacOS/Safari
This yields the designated requirement expression, equivalent to:
>> codesign requirements
inside 0tH.
Again, Apple prints it; 0tH embeds it in the same flow as the rest of the structure.
6. Certificates and CMS – codesign and security cms
codesign will already show you the authority chain at a high level:
codesign -d --verbose=4 /Applications/Safari.app/Contents/MacOS/Safari
You get lines like:
Authority=Software Signing
Authority=Apple Code Signing Certification Authority
Authority=Apple Root CA
If you want to actually decode the CMS blob (the same one 0tH refers to as “CMS Signature”), you need a two-step dance:
# 1. Extract the embedded CMS blob
codesign -d --extract-signature=/tmp/safari.cms \
/Applications/Safari.app/Contents/MacOS/Safari
# 2. Decode it with the Security framework
security cms -D -i /tmp/safari.cms
That is the canonical way to get to the certificate chain, signing time, and other CMS metadata – i.e. what 0tH summarises with:
>> codesign certificates
in a single command.
7. Notarisation Ticket – spctl
To verify notarisation using only system tools, you normally pivot to spctl:
spctl --assess --type execute --verbose=4 \
/Applications/Safari.app/Contents/MacOS/Safari
If the binary is notarized, you will see it explicitly in the output (e.g. source being “Notarized Developer ID”).
0tH, instead, detects the presence of the notarisation ticket directly inside the SuperBlob and reports:
>> codesign notarization
This binary has been notarized by Apple.
Again: canonical tools give you the verdict; 0tH shows you the structure.
The Classical Workflow vs 0tH
If you stay strictly within Apple’s tooling, the practical workflow looks like this:
codesign -d --verbose=4gives high-level signature infocodesign -d --entitlements :-to obtain entitlementscodesign -d --requirements :-to obtain requirementsotool -lto locateLC_CODE_SIGNATUREand thedataoff/datasizedd + hexdump(or your own script) to carve and inspect the SuperBlobcodesign --extract-signature + security cms -D -ito decode the CMS and certificatesspctl --assess --type execute --verbose=4to obtain a notarisation verdict
You absolutely can analyse a code signature end-to-end this way.
It just requires multiple tools, manual correlation of offsets, and quite a bit of patience.
0tH does not replace the canonical tools – you will still use codesign and spctl when you care about the system’s opinion.
What 0tH does is turn that scattered pipeline into a single, coherent, structural view of the LC_CODE_SIGNATURE and its whole ecosystem.
That is my view of “proper CODE_SIG analysis”.
Conclusions
A long and dense post, like the other ones in this series.
LC_CODE_SIGNATURE is one of the most opaque parts of a Mach-O: a nested structure, several blob types, offsets everywhere, and a tendency to hide the one thing you actually need to see.
The goal of this post was simple — make the whole thing visible.
With ASM_o_DETH we looked at the minimal case: an ad-hoc signature, no CMS, no requirements, no entitlements, nothing that could mislead the reader. With 0tH itself we explored a proper fat binary and compared verbosity levels. And with Safari we touched the opposite end of the spectrum: a full production-grade signature with CMS, requirements, dual entitlements, a notarisation ticket and more than a hundred privilege keys.
The point is not that 0tH replaces the platform tools — it doesn’t. It is not even its aim, anyway.
The point is that structural understanding matters, and Apple’s utilities do not give you that view unless you stitch together half a dozen commands and manually track offsets.
0tH collapses that workflow into a consistent, deterministic representation of what the binary actually contains. No magic, no “trust me”, no guesswork — just the raw signature, parsed correctly.
And at the end of the day, this is what a reverse-engineering tool should do: show you what’s really there, not what the system wants you to believe.
Want the deep dive?
If you’re a security researcher, incident responder, or part of a defensive team and you need the full technical details (labs, YARA sketches, telemetry tricks), email me at info@bytearchitect.io or DM me on X (@reveng3_org). I review legit requests personally and will share private analysis and artefacts to verified contacts only.
Prefer privacy-first contact? Tell me in the first message and I’ll share a PGP key.
Subscribe to The Byte Architect mailing list for release alerts and exclusive follow-ups.
Gabriel(e) Biondo
ByteArchitect · RevEng3 · Rusted Pieces · Sabbath Stones