- Tweak原理
- DYLD_INSERT_LIBRARIES
- __RESTRICT段防护
- import
- import
- import
- 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
- import “ViewController.h”
- import
- import
- import “ViewController.h”
- import
- import
- import “ViewController.h”
- import “MyPtraceHeader.h”
- import “InjectCode.h”
- import “MyPtraceHeader.h”
- import “fishhook.h”
Tweak原理
执行
make
命令时,在.theos
的隐藏目录中,编译出obj/debug
目录,包含arm64
、armv7
两种架构,同时生成RedDemo.dylib
动态库
在
arm64
、armv7
目录中,有各自架构的RedDemo.dylib
,而debug
目录中的RedDemo.dylib
,是一个Fat Binary
文件
file RedDemo.dylib
-------------------------
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]
RedDemo.dylib (for architecture armv7): Mach-O dynamically linked shared library arm_v7
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
文件
.deb
格式类似于.ipa
格式
.ipa
包通过AppStore
安装,将.ipa
包中的App
安装到设备中.deb
包通过Cydia
安装,将.deb
包中的动态库安装到设备中执行
make install
命令时,在.deb
包中的动态库,会被安装到设备的/Library/MobileSubstrate/DynamicLibraries
目录中以相同的名称,分别存储
.dylib
和.plist
文件
.dylib
为动态库,而.plist
,记录.dylib
所依附的App
包名
DYLD_INSERT_LIBRARIES
在早期的
dyld
源码中,有进程限制的判断。一旦符合条件,使用DYLD_INSERT_LIBRARIES
环境变量插入的动态库将被清空打开
dyld-519.2.2
源码搜索
DYLD_INSERT_LIBRARIES
进入
dyld.cpp
文件,来到5907
行
DYLD_INSERT_LIBRARIES
为NULL
的判断这段代码的上面,来到
5692
行
- 判断进程限制
- 符合条件,调用
pruneEnvironmentVariables
方法,清空插入的动态库一旦插入的动态库被清空,意味着越狱插件将会全部失效。如果我们找到进程限制的开启条件,并将其使用在项目中,相当于对越狱插件进行了防护
找到
processIsRestricted
设置为true
的代码
- 判断条件有两个,分别是
issetugid
和hasRestrictedSegment
两个函数issetugid
函数,无法在上架的App
中设置,放弃使用hasRestrictedSegment
函数,判断主程序的MachO
是否受限,可以使用进入
hasRestrictedSegment
函数
- 传入主程序的
Header
- 读取
segment
,如果为__RESTRICT
段- 读取
section
,如果为__restrict
节- 如果都存在,返回
trur
,表示进程限制
__RESTRICT段防护
在项目中,添加
__RESTRICT
段,__restrict
节,开启进程限制,对越狱插件进行防护搭建
App
项目,命名:antiTweak
打开
ViewController.m
文件,写入以下代码:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
exit(0);
}
进程限制,是早期
dyld
源码中的逻辑,在低系统下才能生效使用
iOS9.1
系统运行项目,点击屏幕就会闪退搭建
Tweak
插件,附加antiTweak
应用打开
Tweak.x
文件,写入以下代码:```
import
%hook ViewController
-(void)touchesBegan:(NSSet
%end
> 安装插件,启动应用,`touchesBegan`方法被插件`HOOK`。点击屏幕,闪退变为打印
> 为`antiTweak`项目,添加`__RESTRICT`段,`__restrict`节
> 在`Build Setting`的`Other Linker Flags`中,加入以下设置:
>
-Wl,-sectcreate,RESTRICT,restrict,/dev/null
> 编译项目,查看`MachO`文件<br />

> - 成功插入`__RESTRICT`段,`__restrict`节
> 运行项目,点击屏幕闪退。说明插入的动态库已被清空,越狱插件全部失效
> 这种防护手段,在早期系统中比较有效。但在`iOS11`及更高系统中,`dyld`源码发生变化,这种方式已失去作用
#####修改MachO破解
> 在老系统的越狱设备上,遇到使用此方式防护的应用,导致我们的越狱插件无法使用,可以通过修改`MachO`文件破解防护
> 使用`MachOView`打开`MachO`文件<br />

> 修改`Data`值,将`72`改为`73`,`52`改为`53`。只在以前的数值上替换,位数不要改变<br />

> 当`MachO`文件修改后,使用重签名安装应用,此时`__RESTRICT`段和`__restrict`节已经不存在了,进程限制不会启动,越狱插件可正常使用
#####使用dyld源码防护
> 如果是自己的`App`,我们开启了进程限制,如何禁止攻击者的肆意修改呢?
> 借鉴`dyld`的代码,循环读取`segment`和`section`,如果缺少`__RESTRICT`段或`__restrict`节,说明我们的防护代码被人篡改
> 延用`antiTweak`项目,将`dyld`中的代码迁移到项目中
> 打开`ViewController.m`文件,写入以下代码:
> 导入头文件
>
import
import
> 添加宏定义
>
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
> 添加`hasRestrictedSegment`函数,循环读取`segment`和`section`。如果缺少`__RESTRICT`段或`__restrict`节,返回`false`
>
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;
if (strcmp(seg->segname, "__RESTRICT") == 0) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if (strcmp(sect->sectname, "__restrict") == 0)
return true;
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return false; }
> 加入`load`方法,调用防护代码
>
+(void)load{
struct macho_header* mhmh= _dyld_get_image_header(0);
if(hasRestrictedSegment(mhmh)){
NSLog(@”防护代码有效”);
}
else{
NSLog(@”被篡改”);
}
}
> 修改`Other Linker Flags`中的配置,模拟`MachO`被篡改
>
-Wl,-sectcreate,SESTRICT,sestrict,/dev/null
> 运行项目,输出以下结果:
>
antiTweak[2535:549785] 被篡改
> 当检测到`MachO`被篡改,不要使用痕迹明显的代码进行防护,例如:`exit(0)`。此类代码相当于记号,让攻击者很容易找到防护的位置和逻辑
> 高明的防护手段,应该让攻击者不易察觉,在不知不觉中被系统屏蔽封杀
#####白名单检测
> 进程限制的防护手段,仅低版本系统有效。对于高版本系统的防护,我们可以自制白名单进行检测
> 延用`antiTweak`项目
> 整理出`App`依赖库的白名单
> 打开`ViewController.m`文件,写入以下代码:
>
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
> 在未越狱的设备上,运行项目,遍历所有`image`名称<br />

> 打印结果,相当于一份白名单。如果`App`运行时,加载了白名单以外的动态库,该库很可能是被第三方注入的
> 检测注入的动态库
> 打开`ViewController.m`文件,写入以下代码:
>
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++) {
const char* strName = _dyld_get_image_name(intIndex);
if(intIndex==0 || strstr(strList, strName)){
continue;
}
printf("注入动态库:%s\n",strName);
} }
@end
> 在`load`方法中,循环遍历依赖的动态库。如果动态库不是当前`MachO`文件,或者包含白名单中,属于合法库,直接跳过。否则,将其打印
> 当前`MachO`文件,不需要判断,因为沙盒路径无法固定
> 在越狱设备上运行项目,输出很多白名单以外的动态库,其中包含自制的`antiTweakDemo`插件<br />

> 使用此方法进行防护,需要注意以下几点:
> - 在不同系统下运行项目,整理出尽可能完善的白名单
> - 检测到白名单以外的动态库,不要直接处理。这里建议先收集数据,如果此动态库是我们缺漏的,将其补充到白名单中。如果确认是恶意注入,再做处理
> - 白名单列表,由服务端下发,或者将逻辑直接做到服务端
> 白名单写在客户端的弊端:
> - 白名单的字符串,位于`MachO`的常量区,容易被攻击者发现并`HOOK`
> - 当系统更新,可能会出现白名单以外的依赖库,老版本`App`将无法使用
#####ptrace
> `App`可以被`lldb`动态调试,因为`App`被设备中的`debugserver`附加,它会跟踪我们的应用进程(`trace process`),而这一过程利用的就是`ptrace`函数
> `ptrace`是系统内核函数,它可以决定应用能否被`debugserver`附加。如果我们在项目中,调用`ptrace`函数,将程序设置为拒绝附加,即可对`lldb`动态调试进行有效的防护
> `ptrace`在`iOS`系统中,无法直接使用,需要导入头文件
> `ptrace`函数的定义:
>
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
> - `request`:请求`ptrace`执行的操作
> - `pid`:目标进程的`ID`
> - `addr`:目标进程的地址值,和`request`参数有关
> - `data`:根据`request`的不同而变化。如果需要向目标进程中写入数据,`data`存放的是需要写入的数据。如果从目标进程中读数据,`data`将存放返回的数据
> 搭建`App`项目,命名:`antiDebug`
> 导入`MyPtraceHeader.h`头文件
> 打开`ViewController.m`文件,写入以下代码:
>
import “ViewController.h”
import “MyPtraceHeader.h”
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; ptrace(PT_DENY_ATTACH, 0, 0, 0); }
@end
> 使用`Xcode`运行项目,启动后立即退出。使用`ptrace`设置为拒绝附加,只能手动启动`App`
> 也就是说,用户在使用`App`时,不会有任何影响。一旦被`debugserver`附加,就会闪退
> 如果在越狱环境,手动对`App`进行`debugserver`附加呢?
> 找到`antiDebug`进程
>
ps -A | grep antiDebug
12233 ?? 0:00.27 /var/containers/Bundle/Application/5DC00A3B-C095-46D1-9842-A3C35401DD07/antiDebug.app/antiDebug
> 手动对`App`进行`debugserver`附加
>
debugserver localhost:12346 -a 12233
debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87 for arm64. Attaching to process 12233… Segmentation fault: 11
> 同样附加失败,无论以何种方式,都会被`ptrace`函数阻止
#####破解ptrace
> `ptrace`是系统内核函数,被开发者所熟知。`ptrace`的防护痕迹也很明显,手动运行程序正常,`Xcode`运行程序闪退
> 我们在逆向一款`App`时,遇到上述情况,第一时间就会想到`ptrace`防护
> 由于`ptrace`是系统函数,需要间接符号表,我们可以试探性的下一个`ptrace`的符号断点<br />

> `ptrace`的断点命中,我们确定了对方的防护手段,想要破解并非难事
> 延用`antiDebug`项目,模拟应用重签名,注入动态库
> 创建`Inject`动态库,创建`InjectCode`类
> 在`Inject`动态库中,导入`fishhook`,导入`MyPtraceHeader.h`头文件
> 打开`InjectCode.m`文件,写入以下代码:
>
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 Setting
的Other Linker Flags
中配置
◦-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
iOS11
及更高系统,此防护无效修改
MachO
破解
- 使用
MachOView
打开MachO
文件,修改Data
值- 只在以前的数值上替换,不要对其增减,位数不要改变
使用
dyld
源码防护
- 借鉴
dyld
源码,读取segment
和section
。如果缺少__RESTRICT
段或__restrict
节,说明我们的防护代码被人篡改- 检测到程序被篡改,不要使用痕迹明显的代码进行防护,容易暴露
- 尽量让攻击者在不知不觉中被系统屏蔽封杀
白名单检测
- 遍历
image
名称
◦_dyld_image_count()
◦_dyld_get_image_name(i)
- 在不同系统下运行项目,整理出尽可能完善的白名单
- 检测到白名单以外的动态库,不要直接处理
- 白名单列表,由服务端下发,或者将逻辑直接做到服务端
ptrace
- 可阻止
App
被debugserver
附加- 在
iOS
系统中,无法直接使用,需要导入头文件ptrace
函数的定义
◦int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
破解
ptrace
- 防护效果:手动运行程序正常,
Xcode
运行程序闪退- 使用
ptrace
符号断点试探- 使用
fishhook
对ptrace
函数HOOK
- 是
PT_DENY_ATTACH
枚举值,直接返回。其他类型,执行原始函数