有没有办法在iOS应用程序中列出所有混合方法?

时间:2022-07-21 19:34:44

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.

我基本上正在寻找一种方法来检测何时/什么第三方库调配。我最近遇到了一个广告库使用AFNetworking的古怪分叉的情况。 AFNetworking对NSURLSessionTask进行了调整,在某些情况下,这两个混合体并没有很好地发挥作用。我真的希望能够检测和理智检查这种事情,理想情况下甚至在应用程序中保留每个混合方法的版本化转储,这样我们就可以看到谁修补了什么以及风险是什么。谷歌和堆栈溢出搜索只发布了一堆关于如何调整的教程。任何人遇到这个问题或有解决方案?看起来我可能能够使用objc / runtime.h编写代码,但我无法想象我是第一个需要这个的人。

1 个解决方案

#1


8  

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.

这是我能够得到的最接近的,经过几个小时的修补。它涉及使用mach_override的分支,关于加载顺序的几个DYLD怪癖,以及疯狂黑客的胃。

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.

交换方法描述和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.

它似乎与我创建的两个类别一起工作,在加载过程中发生混乱,并且从我对mach_override和DYLD的内部结构的读取,只要你正确设置了库加载顺序,你可以期望在之前初始化它任何其他用户土地代码,如果你把它放在它自己的动态库中。

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:

现在,我无法保证这一点的安全性,但作为一个调试工具保持身边似乎很有用,所以我将我的例子发布到github:

https://github.com/richardjrossiii/mach_override_example

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

这是麻省理工学院的许可,所以请随意使用。

#1


8  

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.

这是我能够得到的最接近的,经过几个小时的修补。它涉及使用mach_override的分支,关于加载顺序的几个DYLD怪癖,以及疯狂黑客的胃。

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.

交换方法描述和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.

它似乎与我创建的两个类别一起工作,在加载过程中发生混乱,并且从我对mach_override和DYLD的内部结构的读取,只要你正确设置了库加载顺序,你可以期望在之前初始化它任何其他用户土地代码,如果你把它放在它自己的动态库中。

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:

现在,我无法保证这一点的安全性,但作为一个调试工具保持身边似乎很有用,所以我将我的例子发布到github:

https://github.com/richardjrossiii/mach_override_example

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

这是麻省理工学院的许可,所以请随意使用。