Hacktricks-skills macos-memory-inspector
Inspect, debug, and analyze macOS/iOS objects in memory using LLDB and Frida. Use this skill whenever the user needs to examine Objective-C or Swift objects at runtime, understand memory layouts, decode type encodings, work with arm64e/PAC pointers, enumerate classes and methods, or perform runtime inspection on macOS applications. Trigger for any task involving memory forensics, reverse engineering, debugging native code, or analyzing object structures in macOS/iOS processes.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/objects-in-memory/SKILL.MDmacOS Memory Inspector
A skill for inspecting and analyzing macOS/iOS objects in memory using LLDB and Frida. This covers Objective-C, Swift, CFRuntime objects, and modern arm64e considerations.
When to use this skill
Use this skill when the user needs to:
- Inspect Objective-C or Swift objects at runtime
- Understand memory layouts of macOS objects
- Decode Objective-C type encodings
- Work with arm64e/PAC (Pointer Authentication Code) pointers
- Enumerate classes, methods, and selectors
- Debug or reverse engineer macOS/iOS applications
- Analyze CFRuntimeClass structures
- Use LLDB or Frida for runtime inspection
Core Concepts
CFRuntimeClass Structure
CF* objects (CoreFoundation) are instances of
CFRuntimeClass. Key fields:
| Field | Purpose |
|---|---|
| Bitwise flags for object behavior |
| ASCII class name |
| Initializer function |
| Copy function |
| Finalizer function |
| Equality comparison |
| Hash function |
| Debug description |
| Custom reference counting |
| Memory alignment requirements |
Objective-C Memory Sections
segment sections:__DATA
- Message references__objc_msgrefs
- Instance variables__objc_ivar
- Class references__objc_classrefs
- Superclass references__objc_superrefs
- Selector references__objc_selrefs
- All Objective-C classes__objc_classlist
- Categories__objc_catlist
- Non-Lazy Categories__objc_nlcatlist
segment sections:__TEXT
- Method names (C-strings)__objc_methname
- Class names (C-strings)__objc_classname
- Method type encodings__objc_methtype
Modern macOS (Apple Silicon):
- Immutable metadata (shared read-only)__DATA_CONST
/__AUTH
- Pointer Authentication entries__AUTH_CONST
- Authenticated GOT entries (replaces legacy__auth_got
)__got
Type Encoding
Objective-C encodes types using single characters:
| Code | Type | Code | Type |
|---|---|---|---|
| int | | unsigned int |
| char | | unsigned char |
| long | | unsigned long |
| long long | | unsigned long long |
| bitfield | | boolean |
| id (object) | | Class |
| char * | | generic pointer |
| SEL (selector) | | undefined |
| array | | struct |
| union |
Example:
- (NSString *)processString:(id)input withOptions:(char *)options andError:(id)error;
Type encoding:
@24@0:8@16*20^@24
Breakdown:
- Return type (NSString *)@24
- self at offset 0@0
- _cmd (selector) at offset 8:8
- input (id) at offset 16@16
- options (char *) at offset 20*20
- error (NSError **) at offset 24^@24
Modern arm64e Considerations
Non-pointer isa:
- On arm64e, the
field is a packed structure, not a raw pointerisa - May include PAC (Pointer Authentication Code)
- Fields:
,nonpointer
,has_assoc
,weakly_referenced
, class pointerextra_rc - Never blindly dereference the first 8 bytes of an Objective-C object
Strip PAC in LLDB:
(lldb) expr -l objc++ -- #include <ptrauth.h> (lldb) expr -l objc++ -- void *raw = ptrauth_strip((void*)0xADDR, ptrauth_key_asda); (lldb) expr -l objc++ -O -- (Class)object_getClass((id)raw)
Tagged Pointers:
- Some Foundation classes encode payload directly in pointer value
- Detection: MSB on arm64, LSB on x86_64
- No regular
in memory - runtime resolves from tag bitsisa - Always use runtime APIs:
orobject_getClass(obj)[obj class]
Swift Objects:
- Pure Swift classes use Swift metadata (not Objective-C
)isa - Use
for introspection:swift-inspectswift-inspect dump-raw-metadata <pid-or-name> swift-inspect dump-arrays <pid-or-name> swift-inspect dump-concurrency <pid-or-name>
Runtime Inspection Commands
LLDB Commands
Print object from raw pointer:
(lldb) expr -l objc++ -O -- (id)0x0000000101234560 (lldb) expr -l objc++ -O -- (Class)object_getClass((id)0x0000000101234560)
Inspect self in breakpoint:
(lldb) br se -n '-[NSFileManager fileExistsAtPath:]' (lldb) r (lldb) po (id)$x0 (lldb) expr -l objc++ -O -- (Class)object_getClass((id)$x0)
Dump Objective-C metadata sections:
(lldb) image dump section --section __DATA_CONST.__objc_classlist (lldb) image dump section --section __DATA_CONST.__objc_selrefs (lldb) image dump section --section __AUTH_CONST.__auth_got
Read class object memory:
(lldb) image lookup -r -n _OBJC_CLASS_$_NSFileManager (lldb) memory read -fx -s8 0xADDRESS_OF_CLASS_OBJECT
Frida Scripts
Enumerate classes and methods:
if (ObjC.available) { // List all classes console.log(Object.keys(ObjC.classes)); // List a class' own methods console.log(ObjC.classes.NSFileManager.$ownMethods); // List all methods (including inherited) console.log(ObjC.classes.NSFileManager.$methods); }
Intercept method calls:
if (ObjC.available) { const impl = ObjC.classes.NSFileManager['- fileExistsAtPath:isDirectory:'].implementation; Interceptor.attach(impl, { onEnter(args) { this.path = new ObjC.Object(args[2]).toString(); console.log('Called with path:', this.path); }, onLeave(retval) { console.log('Result:', retval.toInt32() ? 'true' : 'false'); } }); }
Enumerate selectors:
if (ObjC.available) { // Get all selectors const selectors = ObjC.classes.NSFileManager.$ownMethods; selectors.forEach(sel => { console.log('Selector:', sel); }); }
Common Tasks
Task 1: Inspect an Object at Runtime
LLDB approach:
- Set breakpoint on method or use
to attach to running processprocess attach - Use
orpo
to print the objectexpr - Use
to get the classobject_getClass - Use
to find class metadataimage dump section
Frida approach:
- Attach to process:
frida -U -f com.example.app - Load script to enumerate classes
- Use
to inspect instancesObjC.Object
Task 2: Decode Type Encoding
Given a type encoding string:
- Parse character by character
- Map each code to its type
- Track offsets for method parameters
- Reconstruct the method signature
Task 3: Handle arm64e PAC Pointers
- Check if pointer has PAC bits set
- Use
to remove authenticationptrauth_strip - Use runtime APIs instead of direct dereferencing
- Account for
when hooking__auth_got
Task 4: Inspect Swift Objects
- Use
for non-invasive inspectionswift-inspect - For Frida, use Swift bridge (requires recent version)
- Map Swift types to Objective-C equivalents when possible
Scripts
See the
scripts/ directory for:
- LLDB helper for object inspectionlldb-inspect-object.py
- Frida script to enumerate classesfrida-enum-classes.js
- Decode Objective-C type encodingsdecode-type-encoding.py
- Strip PAC from arm64e pointersarm64e-pac-stripper.py
References
- Apple Open Source: CFRuntime.h
- Apple Open Source: objc-runtime-new.h
- Clang/LLVM: Pointer Authentication
- Frida: Objective-C Runtime