查看原文
其他

iOS LLDB中反反调试分析与实现

xia0 看雪学院 2019-09-17


本文为看雪论坛优秀文章
看雪论坛作者ID:xia0



关于反调试和反反调试,已经有很多人分析过了,也有很多解决方案。

但是在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的方式修改代码段。
 
大致的流程如下:
1. 使用mmap新建一块内存,把这块内存叫做new
2. 使用vm_copy把想要篡改的处于__text段内的内存(把这块内存叫target)拷贝到new里
3. 向new里写入想执行的代码
4. 调用mprotect把new改为rx。因为mmap出来的内存的max_protection是rwx,所以这里mprotect改权限没问题
5. 调用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


一个简单反反调试实验



这里以爱奇艺为例子分析,爱奇艺在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:
[*] target address: 6501024128 and offset: 384
[*] mmap new page: 4572217344 success!
[+] vm_copy success!
[+] mach_vm_write success!
[*] set new page back to r-x success!
[*] vm_region_recurse_64 success!
[*] get page info success!
[+] remap 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



总结



这里只是简单的绕过了ptrace方式的反调试。

针对直接用汇编写的反调试,我的做法是静态内存搜索匹配svc位置,发现是调用26号系统调用则利用内存patch为nop。或者写一个简单的hook代码,hook所有的svc地址,判断寄存器的值然后进行hook即可,这样就能绕过这些反调试机制,再次向Zz和庆哥低头。


>>>>

参考/致谢


https://gist.github.com/piaoger/4ba83f6a954bca113505
https://github.com/comex/substitute/blob/master/lib/darwin/execmem.c
https://www.codercto.com/a/63507.html
https://github.com/jmpews/HookZz/blob/dev/srcxx/UserMode/ExecMemory/code-patch-tool-darwin.cc#L26
https://github.com/gdbinit/readmem/blob/master/readmem/main.c
https://github.com/frida/frida-gum/blob/master/gum/backend-darwin/machexc.h
http://newosxbook.com/src.jl?tree=xnu&file=/libsyscall/mach/mach_vm.c
https://github.com/DaKnOb/memscan/blob/master/main.mm
http://iosre.com/t/topic/8179



- End -




看雪ID:xia0  

https://bbs.pediy.com/user-766212.htm  


*本文由看雪论坛 xia0 原创,转载请注明来自看雪社区





推荐文章++++

打造自己的PE解释器

HW行动 rdpscan后门简单分析

CVE-2018-0802个人浅析

C++中基本数据类型的表现形式

Linux Kernel Exploit 内核漏洞学习(3)-Bypass-Smep




进阶安全圈,不得不读的一本书







      ↙点击下方“阅读原文”,查看原文

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存