开始
关于反调试和反反调试,已经有很多人分析过了,也有很多解决方案。但是在LLDB中做反反调试还没人做过,这也是我一直想解决的一个方案,毕竟本身就是为了调试,那么在LLDB直接输入一行命令就能反反调试应该相对酸爽。本文将介绍一种基于内存单指令patch的方式进行反反调试的方案,大概意思就是通过直接修改代码段的指令来绕过反调试机制。
目前反调试与反反调试情况
这里庆哥写了一遍文章分析了 关于反调试&反反调试那些事
这里简单归纳一下,有如下几种:
- ptrace
- sysctl
- syscall
- SIGTOP
- task_get_exception_ports
这里实际上就大概三种,其他都是基于ptrace的变种。ptrace这个函数是linux就提供的一个接口,常常用作linux系的反调试,本质就是通过26号系统调用来完成的,目前大多反调试都利用该方案。
后面看到庆哥同样提供了一个反反调试的LLDB脚本,不过看了下和我的思路还是不一样的。(差点以为白做了)
正如文章里面写到通过lldb
下断点,然后修改参数,或者直接返回也可以达到反反调试的效果。由于要不断去检查执行状态等,或者程序有定时器定时检测,这个脚本影响性能及变得很卡影响调试体验。不过还是膜庆哥的方案,学习了。
内存patch实现反反调试
说一下大概思路:
- 内存中找到
ptrace
地址 - 将该内存map为
rwx
- 直接将首调指令修改为
ret
指令
刚开始以为就这样简单就完了,结果实际写代码的时候才发现过程远比想象中复杂。
由于iOS不允许直接将代码段map为写权限,这里调用mach_vm_protect
或mprotect
都会异常。但是类似frida、substitute以及hookzz都能进行指令hook。这样说来,肯定是可以修改代码段的。看了下substitute以及frida中关于这块的实现,才发现可以用一种remap的方式修改代码段。
大致的流程如下:
- 使用mmap新建一块内存,把这块内存叫做new
- 使用vm_copy把想要篡改的处于__text段内的内存(把这块内存叫target)拷贝到new里
- 向new里写入想执行的代码
- 调用mprotect把new改为rx。因为mmap出来的内存的max_protection是rwx,所以这里mprotect改权限没问题
- 调用mach_vm_remap把new的内容反映回target里
不过当我写代码测试的时候发现,remap以后整个页数据都变成了0。实在不清楚原因,向Zz求助,Zz直接扔了我他实现这块的代码。我看了以后收益匪浅,只怪之前没分析hookzz的具体实现。后面才知道由于我的设备是iOS12,Zz意思是codesign的问题,hookZz也没支持。于是换了一台iOS9的设备,果然就可以了,向Zz低头。
期间还由于我手残忘记调用mprotect把new改为rx。导致直接执行异常,用memory region
查看地址才知道页保护属性为rw
。
相关代码如下:
1、map new page for patch
// map new page for patch
void *new = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
if (!new ){
NSLog(@"[-] mmap failed!");
return;
}
NSLog(@"[*] new map address:%p", new);
2、start patch
// start patch
kret = vm_copy(self_task, (unsigned long)page_start, 0x1000, (vm_address_t) new);
if (kret != KERN_SUCCESS){
NSLog(@"[-] kr: %d, errno: %d", kret, errno);
return;
}
char patch_ret_ins_data[4] = {0xc0, 0x03, 0x5f, 0xd6}; // ret
memcpy((void *)(new+patch_offset), patch_ret_ins_data, 4);
NSLog(@"[*] new map+offset address:%p", (void *)(new+patch_offset));
3、set new page back to r-x
// set back to r-x
int ret = mprotect(new, 0x1000, PROT_READ | PROT_EXEC);
NSLog(@"[*] ret: %d, errno: %d, addr: %p", ret, errno, new);
4、remap the target page
kret = mach_vm_remap(mach_task_self(), &target, 0x1000, 0,
VM_FLAGS_OVERWRITE, self_task,
(mach_vm_address_t) new, TRUE,
&c, &m, inherit);
if(kret != KERN_SUCCESS){
NSLog(@"[-] kr: %d, errno: %d", kret, errno);
return;
}
NSLog(@"[*] now ptrace_ptr address:%p", ptrace_ptr)
5、clear cache
void* clear_start_ = (void*)page_start + patch_offset;
sys_icache_invalidate (clear_start_, 4);
sys_dcache_flush (clear_start_, 4);
完整的代码在xia0LLDB里面已经集成:https://github.com/4ch12dy/xia0LLDB/blob/master/debugme.py
一个简单反反调试实验
这里以爱奇艺为例子分析,爱奇艺在main函数里面动态调用了ptrace函数进行反调试。
-
后台启动方式启动爱奇艺
xia0 ~ $ issh debug -x backboard /var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/iQiYiPhoneVideo [I]:iproxy process for 2222 port alive, pid=16264 [I]:++++++++++++++++++ Nice to Work :) +++++++++++++++++++++ [I]:iOSRE dir exist [I]:iproxy process for 1234 port alive, pid=16428 [I]:Run ps -e | grep debugserver | grep -v grep; [[ 0 == 0 ]] && (killall -9 debugserver 2> /dev/null) [I]:/iOSRE/tools/debugserver file exist, Start debug... [I]:Run /iOSRE/tools/debugserver 127.0.0.1:1234 -x backboard /var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/iQiYiPhoneVideo
-
LLDB挂上以后在main函数下断点以后直接执行
debugme
命令(lldb) debugme Kill antiDebug by xia0: [*] ptrace target address: 0x1837dc180 and offset: 0x180 [*] mmap new page: 0x1021ec000 success. [+] vm_copy target to new page. [+] patch ret[0xc0 0x03 0x5f 0xd6] with memcpy [*] set new page back to r-x success! [*] get page info done. [+] remap to target success! [*] clear cache success! [+] all done! happy debug~
下面查看对比下patch前后指令ptrace首指令的变化
Patch之前
(lldb) x/12i 0x00000001837dc180 0x1837dc180: 0xf00f26a9 adrp x9, 124119 0x1837dc184: 0x91034129 add x9, x9, #0xd0 ; =0xd0 0x1837dc188: 0xb900013f str wzr, [x9] 0x1837dc18c: 0xd2800350 mov x16, #0x1a 0x1837dc190: 0xd4001001 svc #0x80 0x1837dc194: 0x540000c3 b.lo 0x1837dc1ac ; <+44> 0x1837dc198: 0xa9bf7bfd stp x29, x30, [sp, #-0x10]! 0x1837dc19c: 0x910003fd mov x29, sp 0x1837dc1a0: 0x97ff9b08 bl 0x1837c2dc0 ; cerror 0x1837dc1a4: 0x910003bf mov sp, x29 0x1837dc1a8: 0xa8c17bfd ldp x29, x30, [sp], #0x10 0x1837dc1ac: 0xd65f03c0 ret
Patch之后
(lldb) x/12i 0x1837dc180 0x1837dc180: 0xd65f03c0 ret 0x1837dc184: 0x91034129 add x9, x9, #0xd0 ; =0xd0 0x1837dc188: 0xb900013f str wzr, [x9] 0x1837dc18c: 0xd2800350 mov x16, #0x1a 0x1837dc190: 0xd4001001 svc #0x80 0x1837dc194: 0x540000c3 b.lo 0x1837dc1ac ; <+44> 0x1837dc198: 0xa9bf7bfd stp x29, x30, [sp, #-0x10]! 0x1837dc19c: 0x910003fd mov x29, sp 0x1837dc1a0: 0x97ff9b08 bl 0x1837c2dc0 ; cerror 0x1837dc1a4: 0x910003bf mov sp, x29 0x1837dc1a8: 0xa8c17bfd ldp x29, x30, [sp], #0x10 0x1837dc1ac: 0xd65f03c0 ret
可以发现首地址已经变成了ret指令。
-
执行continue命令,发现爱奇艺已经能够正常调试。
(lldb) c Process 3176 resuming 2019-08-13 17:22:17.283 iQiYiPhoneVideo[3176:161840] [plcrash]: init ok 2019-08-13 17:22:17.790 iQiYiPhoneVideo[3176:161840] -[QYBaikePageDurationManager bk_appDidBecomeActive:] 2019-08-13 17:22:17.922 iQiYiPhoneVideo[3176:161840] CoreData: Failed to load optimized model at path '/var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/QYPGCDataModel.momd/QYPGCDataModel_970.omo' 2019-08-13 17:22:20.477 iQiYiPhoneVideo[3176:161840] OSStatus error: [-34018] Security error has occurred. 2019-08-13 17:22:20.558 iQiYiPhoneVideo[3176:162000] OSStatus error: [-34018] Security error has occurred. 3176:161840] Incorrect NSStringEncoding value 0x8000100 detected. Assuming NSASCIIStringEncoding. Will stop this compatiblity mapping behavior in the near future. ontainers/Data/Application/5C31FE18-9BA4-4B2D-80C6-68BF7F65855F/Library/Application Support/爱奇艺/0_im.sqlite
总结/Todo
这里只是简单的绕过了ptrace方式的反调试,针对直接用汇编写的反调试我的做法是静态内存搜索匹配svc位置,发现是调用26号系统调用则利用内存patch为nop。或者写一个简单的hook代码,hook所有的svc地址,判断寄存器的值然后进行hook即可,这样就能绕过这些反调试机制,再次向Zz和庆哥低头。