🩺 SimpleDebugger

一个 iOS 应用调试器中断点的最小化演示。它可以用于函数 Hook,就像一个非常轻量级的 Frida。 主要用于演示/学习目的,整个实现代码不到 200 行。适用于 arm64 模拟器和设备。

开始使用

像这样创建一个 SimpleDebugger 的实例

SimpleDebugger *debugger = new SimpleDebugger();

Hook 函数

使用 hookFunction(void *originalFunc, void *newFunc) 方法 Hook 函数。originalFunction 必须至少包含 5 条指令,否则您将获得未定义的行为。

添加 Hook 后,所有对 originalFunc 的调用都将转到 newFunc。确保 newFunc 的签名与 originalFunc 完全匹配。 添加 Hook 后,它将在进程的整个生命周期内处于活动状态。 无法从 Hook 函数中调用原始函数。

设置断点

使用 setBreakpoint(vm_address_t address) 方法设置断点。提供的地址必须位于 __TEXT/__text 节中(包含可执行代码的内存区域)。

如果您在不调用 startDebugging 的情况下设置断点,lldb 可以处理这些断点,但是对于 SimpleDebugger 设置的断点,在 lldb 中继续执行经过断点的代码将无法自动工作。 您可以在 lldb 中手动递增程序计数器以继续执行。

响应断点

使用 setExceptionCallback 方法处理断点命中。 提供的回调函数有两个参数,一个是 CPU 状态,另一个是可以调用的函数,用于在命中该断点的线程上继续执行。 调用 startDebugging 开始接收事件。

示例

此示例创建一个调试器并添加一个断点。

#include <SimpleDebugger.h>

void myFunction() { printf("Hello world\n"); }

void breakpointCallback(arm_thread_state64_t state, std::function<void()> sendReply) {
    printf("Got breakpoint with PC: 0x%llx\n", state.__pc);
    sendReply();
}

__attribute__((constructor)) void example(void);
__attribute__((constructor)) void setup() {
  SimpleDebugger *debugger = new SimpleDebugger();
  debugger->setExceptionCallback(breakpointCallback);
  debugger->setBreakpoint((vm_address_t) &myFunction);
  // You must call start debugging to set up the exception server.
  debugger->startDebugging();

  // The breakpoint handler will run before myFunction
  myFunction();
}

此示例 Hook 了 gettimeofday 函数

#include <SimpleDebugger.h>

SimpleDebugger *handler;

int gettimeofday_new(struct timeval *t, void *a) {
  t->tv_sec = 1723532400;
  t->tv_usec = 0;
  return 0;
}

void hookTime() {
  handler = new SimpleDebugger();
  handler->hookFunction((void *) &gettimeofday, (void *) &gettimeofday_new);
}

工作原理

SimpleDebugger 通过修改内存地址的 vm 保护属性使其可写,从而用中断指令覆盖指令。原始指令存储在一个表中,并在命中断点后写回。中断指令由 mach 异常服务器处理。