An iOS/macOS framework that swizzles methods in bulk, using assembly-level trickery & memory mapping to support an arbitrary amount of methods and types.
SwizzleEverything.swizzle { method in
print("Intercepted \(method.selector)")
}
How Apple Hooks Entire Frameworks
The implementation is largely inspired by Apple's Main Thread Checker,
which uses a similar implementation to provide warnings when UIKit methods
are called from the wrong thread. SwizzleEverything can be used
to re-implement that checker with just a few lines of code, to add your own
custom handling:
SwizzleEverything.swizzle(images: ["UIKitCore"], options: .verbose) { method in
guard !method.caller.isSystemFramework else {
return
}
assert(
Thread.isMainThread,
"UIKit method called on a background thread: " +
"-[\(method.methodClass) \(method.selector)]"
)
}I've also found it helpful when reverse engineering other code — it can be extremely useful to have a hook into everything a class or framework might be doing. Even just logging what methods are being called can be helpful, but it's also possible to use variations of this code for more advanced reversing setups.
Swizzling a single method is easy; swizzling many methods is tricky. To deal with different parameter and return types, we replace each method with a unique trampoline that bounces us to a shared assembly handler.
That handler then uses that unique trampoline to figure out what method we were going to, saves all parameter registers onto the stack, calls whatever overall callback we registered, restores the parameter registers, and then branches back to the original implementation.
That's equivalent to how Apple's Main Thread Checker already works, but the standard setup involves knowing in advance exactly how many trampolines you need, which limits the possibilities of the framework.
Instead, this framework uses a technique inspired by imp_implementationWithBlock
(shoutout Landon Fuller for the awesome write-up)
when that runtime feature was first added) to start with a fixed number of trampolines
in the binary and map more of them into memory as needed. This involves a couple tricks
to make data accessible and workaround iOS codesigning requirements, but the end result
is being able to support an arbitrarily large number of methods.
Finally, the framework optionally supports using the private class_replaceMethodsBulk
function to replace all the methods on a single class while only acquiring the runtime lock
once. Because this is a private method, its usage is gated behind a compiler flag.
The Xcode project is set up to produce two frameworks — SwizzleEverything and SwizzleEverythingPrivateAPIs
— to make it easy to pull in either version, depending on your use case.
