时时勤拂拭,勿使惹尘埃

TOC

Categories

iOS(十六)一次通过lldb绕过越狱检测&反反调试实践


0x0 背景

最近在测试一个app时,发现启动app时会进行越狱检测,检测到越狱后会直接退出app,这样常规的越狱绕过工具如needle都是需要等程序运行后再attach,对此场景不太适用,所以这里需要使用lldb来进行绕过。

0x1 越狱检测

0x11 定位检测点

使用ida静态分析app的越狱检测方式,首先尝试通过检索cydiasubstratejailbreak等字符串尝试定位检测位置,但未检索到。从程序运行时间段来看,程序是启动时就会检测越狱并直接退出,故从app启动流程main()函数开始分析(iOS app启动流程可以参考:iOS(七)iOS程序的启动过程,另外main()函数在ida里自动解析为start()函数),在main()函数入口即进行越狱检测(手动改的名):

0x12 检测方式分析

app会进行越狱检测和重打包检测,检测到后立即退出

检测方式也比较简单,都是检查是否存在越狱相关文件,但文件路径都是拼接而成,所以开始通过相关字符串并未检索到:

0x13 绕过方式

绕过方式也比较简单,首先确定退出函数exit()的调用点,目前只有is_jailbreak_exit()中调用 (jailbreak_exit()只检测不退出):

查看is_jailbreak_exit()汇编代码,确定调用exit()的位置,这里使用CBNZ跳转,即当R0不为0时跳转到exit(),所以只需要在这里下断点,修改R0返回值为0即可绕过检测:

0x2 反调试

0x21 反调试方式

mani()函数中除了越狱检测,还有进行了反调试,这里检测方式为:
syscall(26,31,0,0,0);

syscall是系统提供的系统调用函数,实现从用户态切换到内核态,这里的调用号26实际为ptrace,可以在Kernel Syscalls查看:
26. ptrace               801e812c T
ptrace()是从Unix的早期版本开始就提供的一种对运行中的进程进行跟踪和控制的调试工具,通过ptrace可以对另一个进程实现调试跟踪。同时ptrace()还提供了一个非常有用的参数那就是PT_DENY_ATTACH,定义如下(参考自Bugging Debuggers),31即其调用号,这个参数用来告诉系统阻止调试器attach,所以这也是最常用的反调试方案:
#import <dlfcn.h>
#import <sys/types.h>

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif  // !defined(PT_DENY_ATTACH)

void disable_gdb() {
  void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
  ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
  ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
  dlclose(handle);
}
所以这里的syscall(26,31,0,0,0);相当于如下调用:
ptrace(PT_DENY_ATTACH, 0, 0, 0);

0x22 反反调试

绕过ptrace(PT_DENY_ATTACH, 0, 0, 0);方法也很简单,只需要修改PT_DENY_ATTACH调用号31为其他值即可,所以只需在执行_syscall时下断点,修改R1参数为其他值如0即可(0x1F=31):

0x3 lldb实践

0x31 iOS端启动调试进程

由于App运行检测到越狱后会直接退出,所以需要通过debugserver来启动程序:
#进入程序所在文件夹
$ cd /var/mobile/Containers/Bundle/Application/05FC912B-656F-44C9-A82E-58E1BE0DAFC2/*.app
#通过`debugserver`来启动程序
$ debugserver -x backboard 0.0.0.0:1234 ./*

0x32 macOS端通过lldb调试

  1. lldb远程attach:
    macOS上启动lldb后,需要使用process connect connect://<ip>:<port>来远程attach调试进程:
  2. 从上图可知,此时程序还在dyld内部,被调试程序尚未加载,需要通过ni单步执行到程序加载(ni执行下一条指令但不进入函数体,si执行下一条指令会进入函数体)
  3. ni执行到开始加载被调试程序,执行image list -o -f列举当前所有模块,获取被调试程序ASLR偏移地址0x00061000
  4. 获取ASLR偏移地址后,给上文分析的越狱检测和反调试位置下断点,再修改相应寄存器参数即可:
    #越狱检测绕过断点
    (lldb) br s -a '0x00061000+0x0006750C'
    #反调试调用断点
    (lldb) br s -a '0x00061000+0x00067550'
    #c指令运行程序,断在第一个断点越狱检测位置
    (lldb) c
    ...
    ->  0xc850c <+8>:  cbnz   r0, 0xc8516               ; <+18>
    ...
    #查看越狱检测跳转到exit时判断语句寄存器r0的值,当r0为0不跳转
    (lldb) p $r0
    (unsigned int) $2 = 1
    #r0=1,修改r0=0即可绕过越狱检测
    (lldb) register write r0 0
    #c指令运行程序,断在第二个断点反调试位置
    (lldb) c
    ...
    ->  0xc8550 <+52>: blx    0x5a3f84                  ; symbol stub for: syscall
    ...
    #查看syscall第二个参数即寄存器r1的值,31即为PT_DENY_ATTACH
    (lldb) p $r1
    (unsigned int) $3 = 31
    #修改r1为其他值如0,即可绕过反调试
    (lldb) register write r1 0
    #继续执行,已绕过反调试
    (lldb) c
    

iOS(十五)几种App砸壳工具对比


0x0 概述

由于App Store下载的APP全都是经过苹果加密过的ipa包,由于App Store使用Apple ID相关的对称加密算法,因此加密后的ipa包,是无法对其进行反编译的,也无法class-dump,需要对其进行解密才能反编译。
APP程序运行起来都会直接在内存解密出原始代码,所以可以在越狱的设备里面通过内存dump方式提取解密后的程序,这种解密又称为砸壳,以前写过一个blog手工实现了这个工作过程:iOS(九)内存dump应用&手动解密,不过解密之后还需要手动恢复mach-O头信息才能运行。
由于手工解密操作繁琐,所以出现了一些自动化的工具来实现这个过程,常见的解密工具有以下几种:dumpdecrypted、clutch、frida-ios-dump、bfinject。
另外,由于高版本非完美越狱里面,都没有删掉签名验证,所以直接运行都会出现killed 9,需要手动签名之后才能使用,详情参考:iOS(十四)高版本越狱的坑 & killed 9

0x1 dumpdecrypted

dumpdecrypted是最早出现也是最基础的砸壳方式,使用稍微复杂,通过手工注入动态dylib库文件方式来实现,绕过方式较多,比如添加编译参数禁用动态加载dylib库(而且dumpdecrypted也已经5年多未曾更新了)。

0x11 编译dumpdecrypted

由于dumpdecrypted未提供release版本,故需要自行下载编译:
$ git clone https://github.com/stefanesser/dumpdecrypted.git
$ cd dumpdecrypted
$ make
直接make编译得到dumpdecrypted.dylib文件通常都是胖Mach-O结构,导入iOS设备之前需要使用lipo瘦身,并添加签名。

0x12 dumpdecrypted使用方式

  1. 拷贝进越狱设备
    $ scp dumpdecrypted.dylib [email protected]:~/
  2. 连接设备
    $ ssh [email protected]
  3. 寻找目标App路径
    # ps -A
    ...
    7279 ??         0:00.94 /var/containers/Bundle/Application/C438C976-2657-46F1-83BE-07F2DFBAF5A7/SYHDAppPayProject.app/SYHDAppPayProject
    ...
    
  4. 找出对应APP的documents目录
    可以使用cycript:
    $cycript -p <pid>
    cy# [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]
    @[#"file:///var/mobile/Containers/Data/Application/BB2EB568-851B-42F4-AD1F-B9B4D5295755/Documents/"]
    
  5. 注入砸壳,需要将dumpdecrypted.dylib拷贝到APP documents目录下执行
    # cp dumpdecrypted.dylib /var/mobile/Containers/Data/Application/BB2EB568-851B-42F4-AD1F-B9B4D5295755/Documents/
    # cd /var/mobile/Containers/Data/Application/BB2EB568-851B-42F4-AD1F-B9B4D5295755/Documents/
    # DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/containers/Bundle/Application/C438C976-2657-46F1-83BE-07F2DFBAF5A7/SYHDAppPayProject.app/SYHDAppPayProject
    
    砸壳成功后会在目标目录下生成SYHDAppPayProject.decrypted文件。

0x2 clutch

0x21 clutch简述

clutch是之后出现的全自动化砸壳工具,使用简单方便,但失败率较高,尤其arm64版本问题很多。

0x22 使用clutch

  1. 下载并编译生成clutch执行文件
  2. 编译生成的clutch也是胖Mach-O结构,需要瘦身并签名
  3. 将clutch执行文件拷贝到手机里
    scp Clutch [email protected]:/usr/bin
  4. 查看当前手机程序未砸壳的应用
    Clutch -i
  5. 对列出的正版应用砸壳
    使用对应列表号或bundleID进行砸壳
    Clutch -d <com.diary.mood>
    或者
    Clutch -d 12

0x3 frida-ios-dump

0x31 frida-ios-dump简述

frida-ios-dump是基于frida开发的一键砸壳工具,需要配置frida环境,配置过程较为复杂,且有一定失败率。

0x32 安装frida

macOS:
$ sudo pip install frida $ sudo pip install frida-tools

0x33 iOS设备植入frida-server

选择对应firda版本和iOS设备cpu型号的frida-server,如本次为frida-server-12.6.0-ios-arm64.xz
将frida-server拷贝到iOS设备的/usr/bin目录下,并chmod a+x frida-server后启动frida-serverfrida-server已经签名过)

0x34 配置frida-ios-dump

执行sudo pip install -r requirements.txt --upgrade即可,会自动安装依赖
如果设备上的module版本不对,可以手动指定版本安装,如:pip install frida==12.6.0 --user

0x35 使用frida-ios-dump砸壳

  1. usb链接iOS设备
  2. mac公钥~/.ssh/id_rsa.pub导入iOS设备/var/root/.ssh/authorized_keys
  3. 启动iOS设备上的frida-server
  4. 修改frida-ios-dump的dump.py文件,指定iOS设备ip、端口、账号名、密码
  5. 打开目标app
  6. mac上执行$ python dump.py -l查看ios app list
  7. mac上执行python dump.py [BundleID]即可一键砸壳

0x4 bfinject

bfinject在CydiaSubstrate不再支持更新之后出现的hook框架,目前只支持iOS 11,最开始主要用于cycript,后来发现里面内置的砸壳工具使用效果不错(成功率比以上三种都高)。使用参考:iOS(十三)使用 bfinject 注入iOS 11_cycript & 砸壳