iOS LLDB中基于内存单指令patch实现反反调试

开始

关于反调试和反反调试,已经有很多人分析过了,也有很多解决方案。但是在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_protectmprotect都会异常。但是类似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和庆哥低头。

参考/致谢

7 个赞

那么如果svc的号加密了怎么办。ie前面混淆过不是常数写入

svc号混淆后静态内存匹配肯定不行了,不过可以hook svc指令然后读取当时的寄存器来判断。

也就是说remap不能在ios12上应用吗?

可以,Chimera越狱一直没问题(那个bug是我把页大小搞错导致的),unc0ver好像现在的版本把签名问题解决了,不过还没测试

这个remap的方法只能越狱?

重点在mprotect 这个函数

我也写了个简易的指令Patch,利用vm_protect直接修改权限的,越狱可以,不越狱直接闪退,现在想找一个代替这样的方法,就找到了remap.应该可以做到不越狱patch吧,不然写这么多就太复杂了.

1 个赞

看来你没明白什么要remap

那用remap的意义是啥?
remap重新映射的步骤太复杂,如果只能越狱修改的话,大可以用vm_protect,三行代码可以搞定的事为啥还要那么复杂。

即使越狱状态也不能将代码段vm_protect成可写

你看我的源码,是可以的。

老哥 你这个代码怎么用 我使用了 怎么没有效果,是可以用来做反调试吗

此处vm_copy可否换成memcpy,vm_copy是系统调用,在效率上不如memcpy,如果能在保证地址有效情况下应该是可以用memcpy来读取内存吧