[TOC]
修改系统应用
目标:消除对手机桌面的提醒气泡
-
通过cycript分析气泡
- 连接手机并登陆,通过进程列表指令
ps -A
查看springboard
在系统的路径,得到结果:System/Library/CoreServices/SpringBoard.app/SpringBoard
,在该目录下找到springboard
的macho
文件(未进行加密), - 终端进行Mac桌面,将上述MachO拷贝至桌面,可以使用IFunBox和指令,指令:
$scp -r -P 12345 root@localhost:/System/Library/CoreServices/SpringBoard.app/SpringBoard ~/Desktop
,通过file SpringBoard
指令确认SpringBoard: Mach-O 64-bit executable arm64
,通过otool -l SpringBoard | grep c
查看加密信息,并未查找到crypt
字段; - 通过
class-dump
导出SpringBoard的头文件; - 进入
springboard
应用进程cycript -p SpringBoard
,在cy环境中导入自定义cy文件@import cmds
,获取当前的VC,递归打印页面元素,并未找到相关可疑视图。 - 向下一响应者推进,打印KeyWindow的视图层级,找到可疑类型:
SBIconView
,SBIconParallaxBadgeView
,通过对视图进行隐藏显示操作可以得到SBIconParallaxBadgeView
即是气泡视图类;
- 连接手机并登陆,通过进程列表指令
-
编写Tweak代码
- 创建Tweak工程,指定工程名、包名、作者名、依附进程APPID;
- 配置Tweak安装地址和端口:
THEOS_DEVICE_IP
,THEOS_DEVICE_PORT
; - 查看
SBIconParallaxBadgeView
头文件可知,其内部实现了-init方法,所以直接通过暴力方式对-init进行hook,并返回nil; - 对工程执行
make
、make package
、make install
,将插件安装到手机重新SpringBoard即可将气泡消失;
在调试过程中,springBoard的KeyWindow可能随着锁屏之类的操作,发生变化,所以最好不要在调试操作中有其他页面出现。
Tweak在每次编译后都会重新生成一个安装包,版本也会随之增加,当低版本覆盖高版本时安装将会报错。
Tweak原理
工作步骤
- 通过Theos工具创建一个Tweak项目,在项目中编写logos代码;
- 代码完成后,通过
make
工具将.xm文件编译生成目标动态库文件:./.theos/obj/debug/xxx.dylib
; - 通过
make package
指令对xxx.dylib
以及xxx.plist
进行打包,生成.deb
包; - 通过
make install
将.deb
拷贝到手机并进行解压,将xxx.dylib
拷贝到/Library/MobileSubstrate/DynamicLibraries
路径下,完成安装; - 重启Springboard程序,附加
xxx.plist
所指向的进程进行hook;
原理即生效过程
在将插件安装到手机后,重启Springboard后,再次启动hook的目标程序Cydia将扫描DynamicLibraries目录下的plist列表,如果重启的程序的包名包含在plist列表中,那么将通过DYLD_INSERT_LIBRIRES方式将动态库依附到目标程序上,然后通过hook功能将原方法重新指向到hook方法上。
大致流程(从安装插件后):DYLD_INSERT_LIBRIRES ---->fishhook/method_swlizze;
实例:1. 写一个正向小target程序在touchBegin:中打印一句话,运行工程,2. 然后新建一个dylib的工程,在+load方法中对touchBegin方法进行methodSwlizze,打印另外一句话,3. 将dylib拷贝到手机,通过DYLD_INSERT_LIBRIRES将dylib依附到target进程,再次点击,4. 查看前后2次的差别。
DYLD_INSERT_LIBRARIES源码分析
DYLD是一个可执行应用程序,他的源码苹果开源公布在:Source Code。
文章 防止tweak依附,App有高招;破解App保护,tweak留一手中,提及的美团能够强力阻止各种dylib的注入,使得一切tweak均为狗比。其防止注入的方式在DYLD的源码中能找到相应的体现。
阅读源码后,分析DYLD的加载流程:在dyld-519.2.2版中,
-
在5906行开始,加载任何库
// load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
-
在第一步前的还有一个控制流程,在5691行:
if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); }
该判断会根据
gLinkContext.processIsRestricted
中的值决定是否继续加载动态库内容。 -
而控制
gLinkContext.processIsRestricted
是否为真的语句,在4696行:if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { gLinkContext.processIsRestricted = true; }
该部分表明,
issetugid() || hasRestrictedSegment(mainExecutableMH)
其中一个为真即可满足条件,而上架的应用中issetugid()
是禁止使用的,所以hasRestrictedSegment
变得关键。 通过搜索
hasRestrictedSegment
可以找到对应的函数,第4293行:
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;
}
该函数表明,只要macho文件的Header中有名为__RESTRICT
的Segment段、且该段有名为__restrict
的sections,则macho就会阻止进程的依附。
修改RESTRICT段防护注入
为工程的Other Link Flags
添加 -Wl -seccreate __RESTRICT __restrict /dev/null
内容即可在macho中添加 __RESTRICT __restrict
,即可以使hasRestrictedSegment
函数的返回值为true,从而达到防止Tweak的注入问题。
但是测试时发现,添加了该段数据后,在自己工程中添加关联的动态库好像也没法正常在注入了。所以将依赖库的MachOType修改为Static Library,也是正常加载。
- 实例:编写一个Tweak项目,安装到越狱手机,查看Tweak是否依附成功。
破坏防护
利用二进制修改器将MachO中的__restrict
进行篡改,篡改后APP使用MonkeyDev进行重签名,就可以再次对进程进行动态库注入依附了。
利用源码防止MachO被修改
将Mac平台的hasRestrictedSegment
函数拷贝出来,在项目中使用该函数来判断指定段内容是否与期望值一致,否则表示MachO已经被修改了。
在拷贝出来的代码中会有很多未定义的头文件,需要导入
、
。
在loader中有mach_header[_64]
的结构体,表示32、64位的macho的Header。在dyld中有mach_header* _dyld_get_image_header(uint32_t image_index)
函数,该函数通过index获取指定macho的Header,当传入index为0时即是当前启动的App的可执行程序(DYLD启动App时,首先加载的是App的可执行文件(可以通过LLDB:image list
指令查看脚标进行验证))。
导入头文件后需要对部分宏定义进行定义,从dyld.cpp文件中可以拷贝出来,包括LC_SEGMENT_COMMAND
和mach_header[_64]
等。
将该函数调试完成后,就可以在+load中进行调用并传入_dyld_get_image_header
获取的macho_header,验证当前macho的段内容是否与期望的内容一致。
总结
- 攻:一般裸奔环境中可以使用DYLD_INSERT_LIBRARIES进行动态库临时注入;
- 防:通过添加__RESTRICT段可以起到防护进程依附的问题;
- 攻:对加有__RESTRICT字段的防御手段,通过二进制修改该字段的内容并进行代码重签名就可以做到再次依附的效果,如monkeyDev。
- 防:使用DYLD中
hasRestrictedSegment
函数对macho中的__RESTRICT内容进行检测,如果与预期值不同就可以主动退出应用。
花絮
关于ldid
对ldid
的描述:为执行文件添加签名,以便在手机上进行运行,在操作keychain_dumper时出现killed:9的问题的修复,参见:iPhone: keychain dumper – killed 9 problem,其中的重点是:
On the newer versions of iOS (v5) running this tool ends up with killed 9 error.
The problem here is iOS kernel signature checks the binary file at several places. Jailbreak tools cannot patch all these signature checks because it is difficult for them to patch each and every signature check. When we copy keychaindumper to iPhone, it does not have a signature. So running the binary exits with killed 9 message because iOS kernel does not have the signature of keychain_dumper.
To get rid of this problem add the signature of keychain_dumper to kernel cache (list of hashes) by running the below command. After adding the signature, you can run the rest of commands to dump the keychain entries.
ldid -S keychain_dumper
Tweak原理其他解释
iOS逆向之二-一个简单的Tweak插件原理解析:重点
-
输入项目名称、项目包名,作者,需要注入的宿主程序包名和宿主进程名称
Project Name (required): FirstReProj #项目名 Package Name [com.yourcompany.firstreproj]: com.zyt.firstreproj #项目包名 Author/Maintainer Name [aron]: zyt #作者 [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.springboard #需要注入的宿主程序包名 [iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: SpringBoard #宿主进程名称 Instantiating iphone/tweak in firstreproj/... Done.
-
工程目录:
FirstReProj.plist - 注入的宿主程序包名配置文件 Makefile -make文件 Tweak.xm -源码文件,xm格式文件支持c/oc/logo语法,x格式支持logo语法 control -控制文件,保存项目的配置信息
-
原理:
(1) Cydia Substrate 越狱机器插件、软件运行的基础依赖包,提供动态注入的功能 Sbustrate 主要由三部分组成:MobileHooker,MobileLoader,safe mode (1.1) MobileHooker 用于替换系统的方法,这个过程称为Hooking 有两个有 Cydia Substrate 框架提供的方法 MSHookMessageEx 用于注入OC函数 函数原型 void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old) MSHookFunction 用于注入c/c++函数 Logos语法中的 %hook 就是对此函数的封装,代码更简洁、直观 (1.2) MobileLoader 将tweak插件注入到第三方应用程序中(动态注入:ptrace) MobileLoader 查找 /Library/MobileSubstrate/DynamicLibraries 下 plist 文件指定的作用范围,把对应的dylib动态的注入到进程中 safe mode : 不加载第三方驱动,只加载系统自带的驱动 安装了第三方插件导致系统的在启动的时候导致桌面的崩溃,会启动 safe mode,不会重新加载桌面 *可以ssh到iPhone,使用dpkg命令删除Tweak插件包 dpkg -r com.zyt.reapp
-
DEB包解析
使用make package 会在项目目录下面生成package文件夹,里面包含了deb包, DEB包本质上就是一种特殊格式的压缩包,解压里面的内容,可以看到Library的目录结构和安装到iPhone上对应文件目录结构是一致的,是一种映射关系,简单来说,安装deb包就是一个解压操作而已,把deb包中的内容解压到iPhone就完成了安装流程。
坑爹的打印
下面这段logos截图的源码在被我复制到md之前被我折腾了一个小时。一直报错
,就是打印不出来。
源码截图:
从截图看上去没有一点问题,只要有和打印
后跟的内容都打印不来,比如尝试了打印没
、打印了
都打印不出来,本来想记一下logos中是不是奇葩得不支持打印
这个中文符号,结果复制粘贴到.md才发现下面的问题。
源码如下:
#import
%hook ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// NSLog(@"biabsd:%@", @"打打分");
// NSLog(@"sdfrie:%@", @"被hook后打印没有点到我,被hook了");
// NSLog(@"当前输入密码:");
// NSLog(@"当前输入密码:%@", touches);
NSLog(@"打印了");
// %orig;
}
%end
在复制到.md中才发现有一红点,输入法是没法打出来的,也没显示出来。那这个红点是哪里来的呢?
这段文字本来是被放在xib的label.text中的,在打印
后面整好用alt+enter
强行换了行,结果粘贴过来的时候格式依然还在,但是sublime却没显示出来,而是在一行中,当然.md 也没也换行显示。。。就这样一直用了一个小时才觉得是logos奇葩不支持打印
这样的中文符,结果是有特殊的换行符,NSLog不支持这样的特殊换行符。。。。
泪奔啊,,-_-||