writing
Defeating the ptrace Anti-Debugging Protection in the AMap iOS Client破解高德地图 iOS 客户端的 ptrace 反调试保护
· Debugging & reverse engineering
Translated from Chinese — .
After studying the two articles http://www.iosre.com/t/7-2-0-ios/770 and http://bbs.iosre.com/t/ptrace/371, I got my hands dirty and tried it out. I found that AMap (Gaode Maps) version 7.5.4 no longer has the sub function, but instead directly adds the dynamic loading of ptrace in start. As shown below:
(Screenshot was hosted on a now-expired image host and is no longer available.)
So I figured I could just hook dlsym, and when the second argument is “ptrace”, return a fake ptrace function.
As you can see in the figure, AMap doesn’t check the return value of ptrace.
Key code:
#import <substrate.h>
#import <mach-o/dyld.h>
#import <dlfcn.h>
int fake_ptrace(int request, pid_t pid, caddr_t addr, int data){
return 0;
}
void *(*old_dlsym)(void *handle, const char *symbol);
void *my_dlsym(void *handle, const char *symbol){
if(strcmp(symbol,"ptrace") == 0){
return (void*)fake_ptrace;
}
return old_dlsym(handle,symbol);
}
%ctor{
MSHookFunction((void*)dlsym,(void*)my_dlsym,(void**)&old_dlsym);
}
I’ve tested it myself, and it works quite well. Here’s the code for reference: https://github.com/everettjf/iOSREPractise/tree/master/AMap754/amaptest
Addendum on December 28, 2015:
Hooking dlsym above is one approach, but actually hooking ptrace directly is more straightforward.
static int (*oldptrace)(int request, pid_t pid, caddr_t addr, int data);
static int newptrace(int request, pid_t pid, caddr_t addr, int data){
return 0; // just return zero
/*
// or return oldptrace with request -1
if (request == 31) {
request = -1;
}
return oldptrace(request,pid,addr,data);
*/
}
%ctor {
MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"), (void *)newptrace, (void **)&oldptrace);
}
When ptrace’s first argument is 31, it detaches the debugger. So in newptrace you can just return 0 directly, or you can check whether the first argument request equals 31 and change the value of request.
Let me summarize the approach again:
- When the program is running, if attaching the process with
debugserver *:1234 -a BinaryNameproduces asegmentfault 11, it generally means the program internally called ptrace. - To verify whether ptrace was called, you can use
debugserver -x backboard *:1234 /BinaryPath(this is the full path), then set a symbolic breakpointb ptrace, and afterc, look at the location of ptrace’s first line of code. Then usep $lrto find the function’s return address, and based on the ASLR offset fromimage list -o -f, compute the original address. Finally, find the code that calls ptrace in IDA and analyze how ptrace is called. - Start hooking ptrace.
PS: I found that both AMap and Alipay simply call ptrace(31,0,0,0) and don’t handle the return value. (The return value can’t provide any useful meaning anyway.)
学习了 http://www.iosre.com/t/7-2-0-ios/770 和 http://bbs.iosre.com/t/ptrace/371 两篇文章后,上手操作了下。 发现高德地图7.5.4版本已经没有了sub函数,而是直接在start中加入了ptrace的动态加载。如下图:
(截图原图床已过期,链接已失效)
想来可以直接hook dlsym,当第二个参数为”ptrace”时,返回一个假的ptrace函数。
图中可以看到高德地图并没有判断ptrace的返回值。
关键代码:
#import <substrate.h>
#import <mach-o/dyld.h>
#import <dlfcn.h>
int fake_ptrace(int request, pid_t pid, caddr_t addr, int data){
return 0;
}
void *(*old_dlsym)(void *handle, const char *symbol);
void *my_dlsym(void *handle, const char *symbol){
if(strcmp(symbol,"ptrace") == 0){
return (void*)fake_ptrace;
}
return old_dlsym(handle,symbol);
}
%ctor{
MSHookFunction((void*)dlsym,(void*)my_dlsym,(void**)&old_dlsym);
}
自己已测试,还蛮好用。 这是代码,可参考: https://github.com/everettjf/iOSREPractise/tree/master/AMap754/amaptest
2015年12月28日补充:
上面hook dlsym是一种途径,其实直接hook ptrace更直接啦。
static int (*oldptrace)(int request, pid_t pid, caddr_t addr, int data);
static int newptrace(int request, pid_t pid, caddr_t addr, int data){
return 0; // just return zero
/*
// or return oldptrace with request -1
if (request == 31) {
request = -1;
}
return oldptrace(request,pid,addr,data);
*/
}
%ctor {
MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"), (void *)newptrace, (void **)&oldptrace);
}
ptrace 的第一个参数当是31时,会detach调试器。所以,newptrace中可以直接返回0,也可以判断第一个参数request等于31时,更改下request的参数值。
再总结下思路:
- 当程序运行后,使用
debugserver *:1234 -a BinaryName附加进程出现segmentfault 11时,一般说明程序内部调用了ptrace 。 - 为验证是否调用了ptrace 可以
debugserver -x backboard *:1234 /BinaryPath(这里是完整路径),然后下符号断点b ptrace,c之后看ptrace第一行代码的位置,然后p $lr找到函数返回地址,再根据image list -o -f的ASLR偏移,计算出原始地址。最后在 IDA 中找到调用ptrace的代码,分析如何调用的ptrace。 - 开始hook ptrace。
PS:发现高德、支付宝都只是简单的调用ptrace(31,0,0,0),并没有处理返回值。(返回值并无法带来有作用的意义)