Tweak原理

执行make命令时,在.theos的隐藏目录中,编译出obj/debug目录,包含arm64armv7两种架构,同时生成RedDemo.dylib动态库
iOS逆向实战--032:越狱防护 - 图1

arm64armv7目录中,有各自架构的RedDemo.dylib,而debug目录中的RedDemo.dylib,是一个Fat Binary文件

  1. file RedDemo.dylib
  2. -------------------------
  3. RedDemo.dylib: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O dynamically linked shared library arm_v7] [arm64:Mach-O 64-bit dynamically linked shared library arm64]
  4. RedDemo.dylib (for architecture armv7): Mach-O dynamically linked shared library arm_v7
  5. RedDemo.dylib (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64

Tweak的编译产物是动态库,将其注入的方式有两种:

  • 修改MachO文件的Load Commands,注入LC_LOAD_DYLIB (XXX),然后根据路径找到动态库。这种方式对程序的污染比较严重,容易被开发者检测出来
  • 通过DYLD_INSERT_LIBRARIES环境变量,插入动态库

Tweak插件,使用的是方式二,因为程序没有被污染。在MachO中,并没有找到LC_LOAD_DYLIB (XXX)

执行make package命令时,在packages目录中,生成.deb文件。每执行一次打包命令,都会生成一个新的.deb文件
iOS逆向实战--032:越狱防护 - 图2

.deb格式类似于.ipa格式

  • .ipa包通过AppStore安装,将.ipa包中的App安装到设备中
  • .deb包通过Cydia安装,将.deb包中的动态库安装到设备中

执行make install命令时,在.deb包中的动态库,会被安装到设备的/Library/MobileSubstrate/DynamicLibraries目录中

以相同的名称,分别存储.dylib.plist文件

.dylib为动态库,而.plist,记录.dylib所依附的App包名
iOS逆向实战--032:越狱防护 - 图3

DYLD_INSERT_LIBRARIES

在早期的dyld源码中,有进程限制的判断。一旦符合条件,使用DYLD_INSERT_LIBRARIES环境变量插入的动态库将被清空

打开dyld-519.2.2源码

搜索DYLD_INSERT_LIBRARIES

进入dyld.cpp文件,来到5907
iOS逆向实战--032:越狱防护 - 图4

  • DYLD_INSERT_LIBRARIESNULL的判断

这段代码的上面,来到5692
iOS逆向实战--032:越狱防护 - 图5

  • 判断进程限制
  • 符合条件,调用pruneEnvironmentVariables方法,清空插入的动态库

一旦插入的动态库被清空,意味着越狱插件将会全部失效。如果我们找到进程限制的开启条件,并将其使用在项目中,相当于对越狱插件进行了防护

找到processIsRestricted设置为true的代码
iOS逆向实战--032:越狱防护 - 图6

  • 判断条件有两个,分别是issetugidhasRestrictedSegment两个函数
  • issetugid函数,无法在上架的App中设置,放弃使用
  • hasRestrictedSegment函数,判断主程序的MachO是否受限,可以使用

进入hasRestrictedSegment函数
iOS逆向实战--032:越狱防护 - 图7

  • 传入主程序的Header
  • 读取segment,如果为__RESTRICT
  • 读取section,如果为__restrict
  • 如果都存在,返回trur,表示进程限制
__RESTRICT段防护

在项目中,添加__RESTRICT段,__restrict节,开启进程限制,对越狱插件进行防护

搭建App项目,命名:antiTweak

打开ViewController.m文件,写入以下代码:

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. exit(0);
  3. }

进程限制,是早期dyld源码中的逻辑,在低系统下才能生效

使用iOS9.1系统运行项目,点击屏幕就会闪退

搭建Tweak插件,附加antiTweak应用

打开Tweak.x文件,写入以下代码:

```

import

%hook ViewController

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { NSLog(@”🍺🍺🍺🍺🍺”); }

%end

  1. > 安装插件,启动应用,`touchesBegan`方法被插件`HOOK`。点击屏幕,闪退变为打印
  2. > `antiTweak`项目,添加`__RESTRICT`段,`__restrict`
  3. > `Build Setting``Other Linker Flags`中,加入以下设置:
  4. >

-Wl,-sectcreate,RESTRICT,restrict,/dev/null

  1. > 编译项目,查看`MachO`文件<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-0d595010b64adcf7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - 成功插入`__RESTRICT`段,`__restrict`
  4. > 运行项目,点击屏幕闪退。说明插入的动态库已被清空,越狱插件全部失效
  5. > 这种防护手段,在早期系统中比较有效。但在`iOS11`及更高系统中,`dyld`源码发生变化,这种方式已失去作用
  6. #####修改MachO破解
  7. > 在老系统的越狱设备上,遇到使用此方式防护的应用,导致我们的越狱插件无法使用,可以通过修改`MachO`文件破解防护
  8. > 使用`MachOView`打开`MachO`文件<br />
  9. ![](https://upload-images.jianshu.io/upload_images/9297953-8e32b79da033e657.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  10. > 修改`Data`值,将`72`改为`73``52`改为`53`。只在以前的数值上替换,位数不要改变<br />
  11. ![](https://upload-images.jianshu.io/upload_images/9297953-4c34112777a11f14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  12. > `MachO`文件修改后,使用重签名安装应用,此时`__RESTRICT`段和`__restrict`节已经不存在了,进程限制不会启动,越狱插件可正常使用
  13. #####使用dyld源码防护
  14. > 如果是自己的`App`,我们开启了进程限制,如何禁止攻击者的肆意修改呢?
  15. > 借鉴`dyld`的代码,循环读取`segment``section`,如果缺少`__RESTRICT`段或`__restrict`节,说明我们的防护代码被人篡改
  16. > 延用`antiTweak`项目,将`dyld`中的代码迁移到项目中
  17. > 打开`ViewController.m`文件,写入以下代码:
  18. > 导入头文件
  19. >

import

import

  1. > 添加宏定义
  2. >

if LP64

define macho_header mach_header_64

define LC_SEGMENT_COMMAND LC_SEGMENT_64

define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT

define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO

define macho_segment_command segment_command_64

define macho_section section_64

else

define macho_header mach_header

define LC_SEGMENT_COMMAND LC_SEGMENT

define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64

define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64

define macho_segment_command segment_command

define macho_section section

endif

  1. > 添加`hasRestrictedSegment`函数,循环读取`segment``section`。如果缺少`__RESTRICT`段或`__restrict`节,返回`false`
  2. >

static bool hasRestrictedSegment(const struct macho_header mh) { const uint32_t cmd_count = mh->ncmds; const struct load_command const cmds = (struct load_command)(((char)mh)+sizeof(struct macho_header)); const struct load_command cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { const struct macho_segment_command seg = (struct macho_segment_command*)cmd;

  1. if (strcmp(seg->segname, "__RESTRICT") == 0) {
  2. const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
  3. const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
  4. for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
  5. if (strcmp(sect->sectname, "__restrict") == 0)
  6. return true;
  7. }
  8. }
  9. }
  10. break;
  11. }
  12. cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);

}

return false; }

  1. > 加入`load`方法,调用防护代码
  2. >

+(void)load{

struct macho_header* mhmh= _dyld_get_image_header(0);

if(hasRestrictedSegment(mhmh)){
NSLog(@”防护代码有效”); } else{
NSLog(@”被篡改”); } }

  1. > 修改`Other Linker Flags`中的配置,模拟`MachO`被篡改
  2. >

-Wl,-sectcreate,SESTRICT,sestrict,/dev/null

  1. > 运行项目,输出以下结果:
  2. >

antiTweak[2535:549785] 被篡改

  1. > 当检测到`MachO`被篡改,不要使用痕迹明显的代码进行防护,例如:`exit(0)`。此类代码相当于记号,让攻击者很容易找到防护的位置和逻辑
  2. > 高明的防护手段,应该让攻击者不易察觉,在不知不觉中被系统屏蔽封杀
  3. #####白名单检测
  4. > 进程限制的防护手段,仅低版本系统有效。对于高版本系统的防护,我们可以自制白名单进行检测
  5. > 延用`antiTweak`项目
  6. > 整理出`App`依赖库的白名单
  7. > 打开`ViewController.m`文件,写入以下代码:
  8. >

import “ViewController.h”

import

import

@implementation ViewController

+(void)load{ uint32_t intCount = _dyld_image_count();

for (int intIndex=0; intIndex<intCount; intIndex++) { const char* strName = _dyld_get_image_name(intIndex); printf(“%s”,strName); } }

@end

  1. > 在未越狱的设备上,运行项目,遍历所有`image`名称<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-96260b7680eda042.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 打印结果,相当于一份白名单。如果`App`运行时,加载了白名单以外的动态库,该库很可能是被第三方注入的
  4. > 检测注入的动态库
  5. > 打开`ViewController.m`文件,写入以下代码:
  6. >

import “ViewController.h”

import

import

const char* strList = “/private/var/containers/Bundle/Application/E7D8C05C-D581-463F-96AC-791B816265C6/antiTweak…”;

@implementation ViewController

+(void)load{ uint32_t intCount = _dyld_image_count();

for (int intIndex=0; intIndex<intCount; intIndex++) {

  1. const char* strName = _dyld_get_image_name(intIndex);
  2. if(intIndex==0 || strstr(strList, strName)){
  3. continue;
  4. }
  5. printf("注入动态库:%s\n",strName);

} }

@end

  1. > `load`方法中,循环遍历依赖的动态库。如果动态库不是当前`MachO`文件,或者包含白名单中,属于合法库,直接跳过。否则,将其打印
  2. > 当前`MachO`文件,不需要判断,因为沙盒路径无法固定
  3. > 在越狱设备上运行项目,输出很多白名单以外的动态库,其中包含自制的`antiTweakDemo`插件<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-0942f0391327df80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > 使用此方法进行防护,需要注意以下几点:
  6. > - 在不同系统下运行项目,整理出尽可能完善的白名单
  7. > - 检测到白名单以外的动态库,不要直接处理。这里建议先收集数据,如果此动态库是我们缺漏的,将其补充到白名单中。如果确认是恶意注入,再做处理
  8. > - 白名单列表,由服务端下发,或者将逻辑直接做到服务端
  9. > 白名单写在客户端的弊端:
  10. > - 白名单的字符串,位于`MachO`的常量区,容易被攻击者发现并`HOOK`
  11. > - 当系统更新,可能会出现白名单以外的依赖库,老版本`App`将无法使用
  12. #####ptrace
  13. > `App`可以被`lldb`动态调试,因为`App`被设备中的`debugserver`附加,它会跟踪我们的应用进程(`trace process`),而这一过程利用的就是`ptrace`函数
  14. > `ptrace`是系统内核函数,它可以决定应用能否被`debugserver`附加。如果我们在项目中,调用`ptrace`函数,将程序设置为拒绝附加,即可对`lldb`动态调试进行有效的防护
  15. > `ptrace``iOS`系统中,无法直接使用,需要导入头文件
  16. > `ptrace`函数的定义:
  17. >

int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);

  1. > - `request`:请求`ptrace`执行的操作
  2. > - `pid`:目标进程的`ID`
  3. > - `addr`:目标进程的地址值,和`request`参数有关
  4. > - `data`:根据`request`的不同而变化。如果需要向目标进程中写入数据,`data`存放的是需要写入的数据。如果从目标进程中读数据,`data`将存放返回的数据
  5. > 搭建`App`项目,命名:`antiDebug`
  6. > 导入`MyPtraceHeader.h`头文件
  7. > 打开`ViewController.m`文件,写入以下代码:
  8. >

import “ViewController.h”

import “MyPtraceHeader.h”

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad]; ptrace(PT_DENY_ATTACH, 0, 0, 0); }

@end

  1. > 使用`Xcode`运行项目,启动后立即退出。使用`ptrace`设置为拒绝附加,只能手动启动`App`
  2. > 也就是说,用户在使用`App`时,不会有任何影响。一旦被`debugserver`附加,就会闪退
  3. > 如果在越狱环境,手动对`App`进行`debugserver`附加呢?
  4. > 找到`antiDebug`进程
  5. >

ps -A | grep antiDebug

12233 ?? 0:00.27 /var/containers/Bundle/Application/5DC00A3B-C095-46D1-9842-A3C35401DD07/antiDebug.app/antiDebug

  1. > 手动对`App`进行`debugserver`附加
  2. >

debugserver localhost:12346 -a 12233

debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87 for arm64. Attaching to process 12233… Segmentation fault: 11

  1. > 同样附加失败,无论以何种方式,都会被`ptrace`函数阻止
  2. #####破解ptrace
  3. > `ptrace`是系统内核函数,被开发者所熟知。`ptrace`的防护痕迹也很明显,手动运行程序正常,`Xcode`运行程序闪退
  4. > 我们在逆向一款`App`时,遇到上述情况,第一时间就会想到`ptrace`防护
  5. > 由于`ptrace`是系统函数,需要间接符号表,我们可以试探性的下一个`ptrace`的符号断点<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-57b0884323d6dd43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > `ptrace`的断点命中,我们确定了对方的防护手段,想要破解并非难事
  8. > 延用`antiDebug`项目,模拟应用重签名,注入动态库
  9. > 创建`Inject`动态库,创建`InjectCode`
  10. > `Inject`动态库中,导入`fishhook`,导入`MyPtraceHeader.h`头文件
  11. > 打开`InjectCode.m`文件,写入以下代码:
  12. >

import “InjectCode.h”

import “MyPtraceHeader.h”

import “fishhook.h”

@implementation InjectCode

+(void)load{

struct rebinding reb; reb.name=”ptrace”; reb.replacement=my_ptrace; reb.replaced=(void *)&sys_ptrace;

struct rebinding rebs[]={reb}; rebind_symbols(rebs, 1); }

int (*sys_ptrace)(int _request, pid_t _pid, caddr_t _addr, int _data);

int my_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data){

if(_request==PT_DENY_ATTACH){ return 0; }

return sys_ptrace(_request, _pid, _addr, _data); }

@end ```

ptrace_my函数中,如果是PT_DENY_ATTACH枚举值,直接返回。如果是其他类型,系统有特定的作用,需要执行ptrace原始函数

运行项目,进入lldb动态调试,ptrace破解成功

总结

Tweak原理

  • Tweak编译产物是动态库
  • 打包时,将动态库打包成.deb格式
  • 插件安装到/Library/MobileSubstrate/DynamicLibraries目录中
    ◦ 安装.dylib.plist文件
    .plist记录.dylib所依附的App包名
  • Tweak插件使用DYLD_INSERT_LIBRARIES方式,插入动态库

DYLD_INSERT_LIBRARIES

  • 早期dyld源码中,有进程限制的判断(processIsRestricted
  • 启用进程限制,segment存在__RESTRICT段,section存在__restrict
  • 符合进程限制的条件,清空插入动态库,越狱插件失效

__RESTRICT段防护

  • Build SettingOther Linker Flags中配置
    -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
  • iOS11及更高系统,此防护无效

修改MachO破解

  • 使用MachOView打开MachO文件,修改Data
  • 只在以前的数值上替换,不要对其增减,位数不要改变

使用dyld源码防护

  • 借鉴dyld源码,读取segmentsection。如果缺少__RESTRICT段或__restrict节,说明我们的防护代码被人篡改
  • 检测到程序被篡改,不要使用痕迹明显的代码进行防护,容易暴露
  • 尽量让攻击者在不知不觉中被系统屏蔽封杀

白名单检测

  • 遍历image名称
    _dyld_image_count()
    _dyld_get_image_name(i)
  • 在不同系统下运行项目,整理出尽可能完善的白名单
  • 检测到白名单以外的动态库,不要直接处理
  • 白名单列表,由服务端下发,或者将逻辑直接做到服务端

ptrace

  • 可阻止Appdebugserver附加
  • iOS系统中,无法直接使用,需要导入头文件
  • ptrace函数的定义
    int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);

破解ptrace

  • 防护效果:手动运行程序正常,Xcode运行程序闪退
  • 使用ptrace符号断点试探
  • 使用fishhookptrace函数HOOK
  • PT_DENY_ATTACH枚举值,直接返回。其他类型,执行原始函数