Tweak原理与越狱防护
本文不包含具体编写插件的内容,只是从Tweak的原理去探究怎么防护插件的注入。
生成一个Tweak插件
有两种方式生成Tweak插件,一种是MonkeyDev,一种是Theos。
安装MonkeyDev
MonkeyDev安装与说明https://github.com/AloneMonkey/MonkeyDev
Monkey的使用
- MonkeyApp 重签名app,可以选择一个脱壳的ipa直接跑起来debug
- MonkeyPod 通过pod集成插件
- Command-line 命令行工具
- Tweak 越狱插件
我们选择Logos Tweak 来创建插件
项目中xm就是需要编写的hook文件、mm生成的目标文件
plist是注入目标的配置,截图上默认的是springboard应用
build settings里的设置,有三个空的需要填一下,如果是ssl登录,则不需要填写密码。最后一个是安装插件的时候,杀掉的目标进程。
安装Theos
Thoes的安装与使用https://github.com/theos/theos
编译生成Tweak
从编译产物中,可看出是一个dylib动态库文件
原理
Tweak通过dyld insert librarys 环境变量 插入到系统里
插入动态库的核心源码
// load any inserted libraries(越狱的环境都是用这个!)
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
通过dyld源码查看到环境变量sEnv.DYLD_INSERT_LIBRARIES不为空的时候,会插入动态库,所以我们继续看源码环境变量相关的部分
在insert之前pruneEnvironmentVariables这行代码表示移除相关的环境变量,因此我们只要关注!gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache这个判断为true就可以
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
继续查看源码看到hasRestrictedSegment这个函数,Mach-O里如果包含了Restricted段就可以是值为true.
// support chrooting from old kernel
bool isRestricted = false;
bool libraryValidation = false;
// any processes with setuid or setgid bit set or with __RESTRICT segment is restricted
// issetugid 这个函数不能在上架的app使用
if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
isRestricted = true;
}
bool usingSIP = (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0);
uint32_t flags;
if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
// On OS X CS_RESTRICT means the program was signed with entitlements
if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {
isRestricted = true;
}
// Library Validation loosens searching but requires everything to be code signed
if ( flags & CS_REQUIRE_LV ) {
isRestricted = false;
libraryValidation = true;
}
}
gLinkContext.allowAtPaths = !isRestricted;
gLinkContext.allowEnvVarsPrint = !isRestricted;
gLinkContext.allowEnvVarsPath = !isRestricted;
gLinkContext.allowEnvVarsSharedCache = !libraryValidation || !usingSIP;
gLinkContext.allowClassicFallbackPaths = !isRestricted;
gLinkContext.allowInsertFailures = false;
static bool hasRestrictedSegment(const macho_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(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;
//dyld::log("seg name: %s\n", seg->segname);
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;
}
防护
通过上边的源码,得到了一个结论,只要在Mach-O里包含了__RESTRICT段就能防止insert library。图里在otherLinker里增加了几个参数
增加完后,用MachOview查看内容,Mach-O里成功的增加了__RESTRICT段,防护住了应用插件
再突破
修改Mach-O,使用MachOview,修改完成后保存。然后需要重签名后运行,重签名后,bundleId变了,可以通过其他方式监测Hook情况
再防护
应用程序内校验Mach-O情况,通过上边的源码hasRestrictedSegment函数,去查看是否__RESTRICT段被破坏。
后续
hook hasRestrictedSegment 方法,继续突破与防护,永无止境。