一个 iOS 应用调试器中断点的最小化演示。它可以用于函数 Hook,就像一个非常轻量级的 Frida。 主要用于演示/学习目的,整个实现代码不到 200 行。适用于 arm64 模拟器和设备。
像这样创建一个 SimpleDebugger 的实例
SimpleDebugger *debugger = new SimpleDebugger();
使用 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 异常服务器处理。