Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to list all swizzled methods in an iOS app?

I'm essentially looking for a way to detect when/what third party libraries swizzle. I recently ran into a situation where an ad library used an oddball fork of AFNetworking. AFNetworking swizzles NSURLSessionTask, and the two swizzles didn't play nicely under certain circumstances. I'd really like to be able to detect and sanity check this kind of thing, and ideally even keep a versioned dump of every swizzled method in the app so we have some visibility into who's monkey patching what and what the risks are. Google and stack overflow search have turned up nothing but a bunch of tutorial on how to swizzle. Anybody run into this issue or have a solution? It looks like I might be able to code something up using objc/runtime.h but I can't imagine I'm the first person to need this.

like image 646
BTK Avatar asked Jul 20 '16 18:07

BTK


People also ask

What is method swizzling in iOS?

Method swizzling is the process of replacing the implementation of a function at runtime. Swift, as a static, strongly typed language, did not previously have any built-in mechanism that would allow to dynamically change the implementation of a function.

What is method swizzling in iOS Swift?

So what is method swizzling? Method swizzling is the process of changing the implementation of an existing selector at runtime. Speak in a easy-to-understand way, method swizzling acts like swap(a, b) function. It will takes implementation of function 1 and function 2 and swap.

How do I disable method swizzling?

Method swizzling in Firebase Cloud Messaging Developers who prefer not to use swizzling can disable it by adding the flag FirebaseAppDelegateProxyEnabled in the app's Info. plist file and setting it to NO (boolean value).


1 Answers

Here's the closest I was able to get, with a few hours of tinkering. It involves using a fork of mach_override, a couple of DYLD quirks regarding load order, and a stomach for crazy hacks.

It will only work on the simulator, but that should suffice for the use case you seem to have (I certainly hope you don't have device-only dependencies).

The meat of the code looks something like this:

#include <objc/runtime.h>
#include <mach_override/mach_override.h>

// It is extremely important that we have DYLD run this constructor as soon as the binary loads. If we were to hook
// this at any other point (for example, another category on NSObject, in the main application), what could potentially
// happen is other `+load` implementations get invoked before we have a chance to hook `method_exchangeImplementation`,
// and we don't get to see those swizzles.
// It is also extremely important that this exists inside its own dylib, which will be loaded by DYLD before _main() is
// initialized. You must also make sure that this gets loaded BEFORE any other userland dylibs, which can be enforced by
// looking at the order of the 'link binary with libraries' phase.
__attribute__((constructor))
static void _hook_objc_runtime() {
  kern_return_t err;
  MACH_OVERRIDE(void, method_exchangeImplementations, (Method m1, Method m2), &err) {
    printf("Exchanging implementations for method %s and %s.\n", sel_getName(method_getName(m1)), sel_getName(method_getName(m2)));

    method_exchangeImplementations_reenter(m1, m2);
  }
  END_MACH_OVERRIDE(method_exchangeImplementations);

  MACH_OVERRIDE(void, method_setImplementation, (Method method, IMP imp), &err) {
    printf("Setting new implementation for method %s.\n", sel_getName(method_getName(method)));

    method_setImplementation_reenter(method, imp);
  }
  END_MACH_OVERRIDE(method_setImplementation);
}

Which is surprisingly simple, and produces output like this:

Exchanging implementations for method description and custom_description.

There is no good way (without using a breakpoint and looking through the stack trace) to figure out which class is being swizzled, but for most things, just taking a peek at the selectors should give you a hint about where to go from there.

It appears to work with a couple of categories that I've created that swizzle during +load, and from my reading of mach_override and DYLD's internals, as long as you have your library load order properly setup, you can expect this to be initialized before any other user-land code, if you put it in it's own dynamic library.

Now, I can't vouch for safety of this, but it seems useful to keep around as a debugging tool, so I've published my example to github:

https://github.com/richardjrossiii/mach_override_example

It's MIT licensed, so feel free to use as you see fit.

like image 172
Richard J. Ross III Avatar answered Oct 16 '22 17:10

Richard J. Ross III