又到了发布时刻。这一次,我们将 iOS 支持提升到新的层次,同时带来了一系列扎实的质量 改进。我也很高兴地宣布,最近我加入了 NowSecure; 这个版本如此出色绝非巧合。

先从一项全新的 iOS 功能说起。现在可以列出已安装的应用,frida-ps 能为你完成这件事:

$ frida-ps -U -a
  PID NAME        IDENTIFIER
10582 Facebook    com.facebook.Facebook
11066 IRCCloud    com.irccloud.IRCCloud
  451 Mail        com.apple.mobilemail
10339 Mailbox     com.orchestra.v2
 6866 Messages    com.apple.MobileSMS
10626 Messenger   com.facebook.Messenger
11043 Settings    com.apple.Preferences
10542 Skype       com.skype.skype
11218 Slack       com.tinyspeck.chatlyio
11052 Snapchat    com.toyopagroup.picaboo
$

加上 -i 选项后,它还会列出所有已安装的应用,而不只是当前正在运行的应用。

你选择的语言绑定也能使用此功能,例如 Python:

>>> import frida
>>> iphone = frida.get_usb_device()
>>> print("\n".join(map(repr, iphone.enumerate_applications())))
Application(identifier="com.google.ios.youtube", name="YouTube")
Application(identifier="com.toyopagroup.picaboo", name="Snapchat")
Application(identifier="com.skype.skype", name="Skype", pid=10542)
…
>>>

这很不错,但你是否还想在这些应用启动早期就进行插桩?现在也可以了,只需让我们启动 一个应用标识符:

$ frida-trace -U -f com.toyopagroup.picaboo -I "libcommonCrypto*"

或者在 API 层完成:

>>> import frida
>>> iphone = frida.get_usb_device()
>>> pid = iphone.spawn(["com.toyopagroup.picaboo"])
>>> snapchat = iphone.attach(pid)
>>> …apply instrumentation…
>>> iphone.resume(pid)

请注意,为了最大限度提高互操作性,启动早期阶段借助了 Cydia Substrate;毕竟多个 框架都向 launchd 注入代码、彼此发生冲突并不是好事。不过这是一个软依赖:如果在 未安装 Substrate 时尝试用应用标识符调用 spawn(),我们会抛出异常。

因此,在 iOS 应用启动早期进行插桩非常酷。但这些应用通常会大量使用 Objective-C API; 想对它们插桩时,我们经常不得不创建新的 Objective-C 类,以便建立插在应用与 API 之间 的 delegate。如果能用纯 JavaScript 创建这样的 Objective-C 类岂不更好?现在可以了:

const MyConnectionDelegateProxy = ObjC.registerClass({
  name: 'MyConnectionDelegateProxy',
  super: ObjC.classes.NSObject,
  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
  methods: {
    '- init': function () {
      const self = this.super.init();
      if (self !== null) {
        ObjC.bind(self, {
          foo: 1234
        });
      }
      return self;
    },
    '- dealloc': function () {
      ObjC.unbind(this.self);
      this.super.dealloc();
    },
    '- connection:didReceiveResponse:': function (conn, resp) {
      /* this.data.foo === 1234 */
    },
    /*
     * But those previous methods are declared assuming that
     * either the super-class or a protocol we conform to has
     * the same method so we can grab its type information.
     * However, if that's not the case, you would write it
     * like this:
     */
    '- connection:didReceiveResponse:': {
      retType: 'void',
      argTypes: ['object', 'object'],
      implementation(conn, resp) {
      }
    },
    /* Or grab it from an existing class: */
    '- connection:didReceiveResponse:': {
      types: ObjC.classes
          .Foo['- connection:didReceiveResponse:'].types,
      implementation(conn, resp) {
      }
    },
    /* Or from an existing protocol: */
    '- connection:didReceiveResponse:': {
      types: ObjC.protocols.NSURLConnectionDataDelegate
          .methods['- connection:didReceiveResponse:'].types,
      implementation(conn, resp) {
      }
    },
    /* Or write the signature by hand if you really want to: */
    '- connection:didReceiveResponse:': {
      types: 'v32@0:8@16@24',
      implementation(conn, resp) {
      }
    }
  }
});

const proxy = MyConnectionDelegateProxy.alloc().init();
/* use `proxy`, and later: */
proxy.release();

不过,大多数时候你会希望构建一个代理对象,把所有调用继续传递下去,只对真正关心的 少数方法记录日志。看看这个:

const MyConnectionDelegateProxy = ObjC.registerProxy({
  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
  methods: {
    '- connection:didReceiveResponse:': function (conn, resp) {
      /* fancy logging code here */
      /* this.data.foo === 1234 */
      this.data.target
          .connection_didReceiveResponse_(conn, resp);
    },
    '- connection:didReceiveData:': function (conn, data) {
      /* other logging code here */
      this.data.target
          .connection_didReceiveData_(conn, data);
    }
  },
  events: {
    forward(name) {
      console.log('*** forwarding: ' + name);
    }
  }
});

const method = ObjC.classes.NSURLConnection[
    '- initWithRequest:delegate:startImmediately:'];
Interceptor.attach(method.implementation, {
  onEnter(args) {
    args[3] = new MyConnectionDelegateProxy(args[3], {
      foo: 1234
    });
  }
});

Objective-C 部分就是这些。得益于 @marc1006, Dalvik 集成也获得了用于枚举已加载类的新 API;他还修复了静态方法处理,以及覆盖实现 无法返回布尔值的问题。

@Tyilo 也贡献了许多出色改进:增强 ObjC 集成、打磨 REPL、添加枚举 malloc 范围的 API,并为 NativePointer 添加一些便利 API。

与此同时,@s1341 一直努力将 Frida 移植到 QNX, 成果非常出色,目前已非常接近完美运行。

下面快速浏览其余变更:

4.0.1:

  • objc:支持更多类型
  • frida-trace:修复 ObjC 跟踪回归

4.0.2:

  • frida-node:修复 pixels 属性的编码

4.0.3:

  • frida-repl:修复 Windows 回归

4.0.5:

  • objc:支持更多类型并改进类型检查
  • objc:arm64 现在可正常工作
  • frida-repl:允许创建变量

4.0.6:

  • platform:支持向 send() 传递普通数据数组
  • arm:支持重定位 cbz/cbnz 指令

4.1.0:

  • platform:修复会写入 stdout 的子进程启动问题
  • platform:修复 NativeCallback 对 bool/int8/uint8 返回值的处理(此前这会 阻止 Dalvik 方法覆盖返回 false)
  • platform:允许 Memory.readByteArray() 使用小于 1 的长度
  • arm:支持重定位 ldrpc t2 指令
  • arm:改进重定向解析器
  • arm64:修复 adrp 指令重定位
  • arm64:支持重定位 PC 相对 ldr 指令
  • dalvik:添加 Dalvik.enumerateLoadedClasses()
  • dalvik:修复静态方法处理
  • python:修复 Windows 上的 console.log()
  • frida-repl:错误修复与改进
  • frida-trace:跟踪 ObjC 方法时支持 glob

4.1.1:

  • platform:为 enumerate_applications() 添加缺失的 pid 字段

4.1.2:

  • objc:类与代理创建 API
  • objc:新增用于枚举协议的 ObjC.protocols API

4.1.3:

  • platform:调用 NativeFunction 时释放 V8 锁,以改进并发能力
  • platform:添加 Process.getModuleByName(name)
  • platform:更快速、更稳健的 detach
  • python:提高 CLI 工具稳定性
  • frida-repl:以 prompt-toolkit 替换 readline

4.1.4:

  • platform:更快速、更稳健的 teardown
  • frida-server:收到 SIGINT 和 SIGTERM 时执行清理

4.1.5:

  • frida-ps:添加列出应用的支持

4.1.6:

  • platform:修复 Mac、iOS 和 Linux 上 spawn 时的崩溃
  • platform:添加 NativePointer.compare() 和 NativePointer.equals()
  • platform:添加 Process.enumerateMallocRanges{,Sync}()
  • frida-trace:停止操作从 Enter 改为 Ctrl+C
  • frida-trace:修复 iOS 应用启动
  • frida-repl:在自动补全中加入原型名称

4.1.7:

  • python:提高 CLI 工具稳定性

本次内容就是这些。请在网上分享这篇文章,帮助我们传播消息。作为一个开源项目, 我们的规模仍然很小,因此口耳相传对我们意义重大。

尽情享用吧!