Understanding the Objective-C Runtime
The Objective-C runtime is a dynamic library that provides the foundation for Objective-C’s object-oriented features. Unlike statically compiled languages, Objective-C resolves method calls, manages objects, and handles message passing at runtime.
Key Runtime Capabilities
Message Dispatch All method calls go through objc_msgSend for dynamic dispatch
Introspection Query classes, methods, properties, and instance variables at runtime
Dynamic Modification Add methods, exchange implementations, and modify classes on-the-fly
Type Encoding Runtime type information stored as encoded strings
This dynamic nature makes Objective-C extremely powerful for reverse engineering - you can inspect and modify almost everything at runtime.
Runtime Architecture
Message Sending
Every method call in Objective-C is actually a message send:
// Source code
[myObject doSomethingWithValue:42];
// Compiled to
objc_msgSend(myObject, @selector(doSomethingWithValue:), 42);
The runtime performs these steps:
Selector Lookup
Find the method implementation (IMP) for the selector in the class’s method list
Cache Check
Check the method cache for recent lookups (optimization)
Inheritance Walk
If not found, walk up the inheritance chain to superclasses
Message Forwarding
If still not found, trigger message forwarding mechanism
Execute IMP
Call the function pointer (IMP) with the receiver and arguments
Class Structure
At runtime, each Objective-C class is represented by a Class structure containing:
struct objc_class {
Class isa; // Metaclass pointer
Class super_class; // Superclass pointer
const char * name; // Class name
long version;
long info;
long instance_size;
struct objc_ivar_list * ivars; // Instance variables
struct objc_method_list ** methodLists; // Methods
struct objc_cache * cache; // Method cache
struct objc_protocol_list * protocols; // Protocols
};
Every class also has a metaclass - a class that describes the class itself. This is how class methods work in Objective-C.
Class Dumping
Class dumping extracts the interface declarations of all classes in a binary, revealing the app’s structure without access to source code.
Why Class Dump?
Class dumping provides:
Complete class hierarchies and inheritance relationships
All public, private, and internal method signatures
Property declarations and instance variable names
Protocol conformances
Type information for method parameters and return values
class-dump
dsdump
Frida (Runtime)
The classic command-line tool for extracting class information: # Install via Homebrew
brew install class-dump
# Dump classes from an app binary
class-dump /path/to/app/binary > headers.h
# Dump specific architecture on universal binaries
class-dump --arch arm64 /path/to/app/binary > headers.h
class-dump only works on unencrypted binaries. App Store apps must be decrypted first using tools like Clutch or frida-ios-dump.
Modern Swift-compatible class dumper: # Clone and build
git clone https://github.com/DerekSelander/dsdump
cd dsdump && make
# Dump classes including Swift types
dsdump --objc --swift /path/to/binary
# Output to file
dsdump --objc --swift /path/to/binary -o output.txt
Dump classes from a running process: // List all classes
ObjC . enumerateLoadedClasses ({
onMatch : function ( className ) {
console . log ( className );
},
onComplete : function () {}
});
// Dump specific class methods
var className = "AppDelegate" ;
var methods = ObjC . classes [ className ]. $ownMethods ;
console . log ( "[*] Methods of " + className + ":" );
methods . forEach ( function ( method ) {
console . log ( " " + method );
});
Class Dump Output Example
@interface UserManager : NSObject
{
NSString *_currentUserId;
BOOL _isPremiumUser;
NSMutableDictionary *_userCache;
}
@property (nonatomic, copy) NSString *currentUserId;
@property (nonatomic, assign) BOOL isPremiumUser;
- (id)initWithUserId:(NSString *)userId;
- (void)loginWithEmail:(NSString *)email password:(NSString *)password completion:(void (^)(BOOL success, NSError *error))completion;
- (void)logout;
- (BOOL)isUserAuthenticated;
- (void)updateUserProfile:(NSDictionary *)profileData;
- (NSDictionary *)getUserData;
@end
This reveals:
Three instance variables (including their types)
Two properties
Six methods with complete signatures
The superclass (NSObject)
Method Listing
Beyond class dumping, you can enumerate and inspect methods at runtime.
Runtime Introspection APIs
Get all registered classes: int numClasses = objc_getClassList(NULL, 0);
Class *classes = (Class *)malloc(sizeof(Class) * numClasses);
objc_getClassList(classes, numClasses);
Get all methods of a class: unsigned int methodCount;
Method *methods = class_copyMethodList([MyClass class], &methodCount);
for (int i = 0; i < methodCount; i++) {
SEL selector = method_getName(methods[i]);
NSLog(@"%@", NSStringFromSelector(selector));
}
free(methods);
Get all properties: unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([MyClass class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
const char *name = property_getName(properties[i]);
NSLog(@"%s", name);
}
free(properties);
Get instance variables: unsigned int ivarCount;
Ivar *ivars = class_copyIvarList([MyClass class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
const char *name = ivar_getName(ivars[i]);
const char *type = ivar_getTypeEncoding(ivars[i]);
NSLog(@"%s (%s)", name, type);
}
free(ivars);
Method Signature Analysis
Method signatures include type encodings:
- (void)setAge:(int)age; // v@:i (void, object, selector, int)
- (NSString *)name; // @@: (object, object, selector)
- (BOOL)isValid:(id)obj; // B@:@ (BOOL, object, selector, object)
Type Codes
@ = object
: = SEL
v = void
i = int
B = BOOL
f = float
d = double
Modifiers
^ = pointer
* = char*
# = Class
r = const
n = in
o = out
Structures
{name=...} = struct
[type] = array
(name=...) = union
bN = bitfield
Runtime Manipulation
The Objective-C runtime allows dynamic modification of classes and methods.
Adding Methods at Runtime
// Define a new method implementation
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method called!");
}
// Add method to a class
Class myClass = [MyClass class];
class_addMethod(myClass,
@selector(dynamicMethod),
(IMP)dynamicMethodIMP,
"v@:");
// Now you can call it
MyClass *obj = [[MyClass alloc] init];
[obj dynamicMethod];
Method Replacement
// Get the original method
Method originalMethod = class_getInstanceMethod([MyClass class], @selector(originalMethod));
// Define replacement implementation
IMP newIMP = imp_implementationWithBlock(^(id self) {
NSLog(@"Replaced implementation");
});
// Replace it
method_setImplementation(originalMethod, newIMP);
Modifying methods at runtime can cause crashes or unexpected behavior. Always test thoroughly and have fallback mechanisms.
Associated Objects
Add properties to existing classes without subclassing:
// Define a key (use static to ensure uniqueness)
static char kAssociatedObjectKey;
// Set associated object
objc_setAssociatedObject(myObject,
&kAssociatedObjectKey,
@"Custom data",
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Get associated object
NSString *data = objc_getAssociatedObject(myObject, &kAssociatedObjectKey);
Association policies:
OBJC_ASSOCIATION_ASSIGN - Weak reference
OBJC_ASSOCIATION_RETAIN_NONATOMIC - Strong reference
OBJC_ASSOCIATION_COPY_NONATOMIC - Copy the value
OBJC_ASSOCIATION_RETAIN - Strong, atomic
OBJC_ASSOCIATION_COPY - Copy, atomic
Frida for Runtime Inspection
Frida is the most powerful tool for runtime manipulation of iOS apps.
Basic Frida Script
// Attach to running process
setTimeout ( function () {
Java . perform ( function () {
// List all loaded classes
console . log ( "[*] Enumerating classes..." );
ObjC . enumerateLoadedClasses ({
onMatch : function ( className ) {
if ( className . includes ( "User" )) {
console . log ( "[+] Found: " + className );
// Get the class
var cls = ObjC . classes [ className ];
// List methods
console . log ( " Methods:" );
cls . $ownMethods . forEach ( function ( method ) {
console . log ( " " + method );
});
}
},
onComplete : function () {
console . log ( "[*] Enumeration complete" );
}
});
});
}, 0 );
Method Hooking with Frida
// Hook a specific method
var targetClass = ObjC . classes . LoginViewController ;
var targetMethod = targetClass [ "- loginWithEmail:password:completion:" ];
Interceptor . attach ( targetMethod . implementation , {
onEnter : function ( args ) {
// args[0] = self
// args[1] = selector
// args[2] = first argument (email)
// args[3] = second argument (password)
var email = ObjC . Object ( args [ 2 ]);
var password = ObjC . Object ( args [ 3 ]);
console . log ( "[*] Login attempt:" );
console . log ( " Email: " + email . toString ());
console . log ( " Password: " + password . toString ());
},
onLeave : function ( retval ) {
console . log ( "[*] Login method returned" );
}
});
Running Frida Scripts
# List running processes
frida-ps -Ua
# Attach to app and run script
frida -U -f com.example.app -l frida-script.js
# Attach to running app
frida -U "App Name" -l frida-script.js
# Spawn and attach
frida -U -f com.example.app --no-pause -l frida-script.js
Use frida-trace for quick method tracing without writing scripts: frida-trace -U -f com.example.app -m "-[LoginViewController *]"
Swift Runtime Considerations
While Swift uses a different runtime model, it still interfaces with Objective-C:
Swift-ObjC Interoperability
// This Swift class is exposed to ObjC runtime
@objc class MySwiftClass : NSObject {
@objc func myMethod () {
print ( "Called from ObjC or Frida" )
}
}
// This one is NOT (pure Swift)
class PureSwiftClass {
func pureSwiftMethod () {
print ( "Not accessible from ObjC runtime" )
}
}
Swift classes marked with @objc or inheriting from NSObject are exposed to the Objective-C runtime and can be inspected/manipulated.
Pure Swift classes use static dispatch and vtables. They’re not accessible through the ObjC runtime but can still be analyzed with Swift-specific tools.
Swift symbols are name-mangled. Use swift-demangle or the SwiftNameDemangler.py script to decode them.
Practical Analysis Workflow
Decrypt the Binary
Use Clutch, frida-ios-dump, or CrackerXI to decrypt App Store apps
Class Dump
Extract all class headers to understand the app structure class-dump MyApp > headers.h
Static Analysis
Load the binary in Ghidra or Hopper for disassembly analysis
Identify Target Methods
Find interesting methods from class dumps (authentication, encryption, etc.)
Runtime Hooking
Use Frida to hook and inspect method calls at runtime
Test and Validate
Verify your understanding by modifying behavior and observing results
Common Runtime Patterns
Singleton Detection
// Find singleton accessor methods
ObjC . enumerateLoadedClasses ({
onMatch : function ( className ) {
var cls = ObjC . classes [ className ];
if ( cls . $methods . some ( m => m . includes ( "sharedInstance" ) || m . includes ( "shared" ))) {
console . log ( "[+] Potential singleton: " + className );
}
},
onComplete : function () {}
});
// Hook common crypto methods
var CCCrypt = Module . findExportByName ( "libcommonCrypto.dylib" , "CCCrypt" );
Interceptor . attach ( CCCrypt , {
onEnter : function ( args ) {
console . log ( "[*] CCCrypt called" );
console . log ( " Key: " + hexdump ( args [ 3 ], { length: 32 }));
}
});
Best Practices
Combine Approaches Use both static and dynamic analysis for complete understanding
Start Broad Begin with class dumps and general exploration before diving deep
Document Findings Keep detailed notes on class relationships and method purposes
Test Safely Use a jailbroken test device, not your primary device
Cycript
Passionfruit
objection
Interactive runtime exploration: # Attach to running app
cycript -p "App Name"
# Explore runtime
cy# [UIApp keyWindow]
cy# ObjectiveC.classes
cy# choose ( LoginViewController )
Web-based iOS app analysis: npm install -g passionfruit
passionfruit
# Open http://localhost:31337
Frida-based mobile exploration toolkit: objection -g "App Name" explore
# Inside objection
ios hooking list classes
ios hooking search methods LoginViewController
ios hooking watch method "-[LoginViewController loginWithEmail:password:completion:]"
Further Reading
Swift Name Mangling Decode Swift mangled names in binaries
Method Swizzling Detect and analyze method swizzling