这里是基于老版本开源的 CydiaSubstrate 源码进行分析,一定程度上可以印证现版本的 CydiaSubstrate 注入流程。
为了解答下面的问题:
CyidaSubstrate
是什么时候被加载的?
CydiaSubstrate
是怎么注入到每一个 APP 中的?
Cydia 结构
在越狱手机的 /Library/Frameworks/CydiaSubstrate.framework
,可以看到CyidaSubstrate 主要分为以下几个部分:
CydiaSubstrate(libsubstrate.dylib)
提供核心MSHookFunction
,MSHookMessageEx
等 hook 功能SubstrateBootstrap.dylib
提供 CydiaSubstrate 的引导SubstrateLauncher.dylib
用于启动引导SubstrateLoader.dylib
负责加载/Library/MobileSubsrate/
目录下的对应动态库
查找启动相关的代码
为了了解每一个对应的二进制是如何产生的,就需要去检查它是如何被编译出来的。
查看它的编译文件 darwin.mk
|
|
可以检索到对应的文件,我们的关键带你就落在下面这些文件上
|
|
Boostrap.cpp
文件的源代码如下:
|
|
做的工作很简单,即动态加载一些必要的动态库,并且核心是为了引导 SubstrateLoader.dylib
的加载。
DarwinLoader.cpp
这个动态库被加载时,会做安全模式的检查,如果不是安全模式,则遍历
|
|
目录下的所有 *.plist
文件,根据文件描述的信息来选择加载动态库。这就是为什么我们编写 Cydia 插件时,我们的代码可以正确的被加载并且完成注入。
在安全模式下会跳过加载的流程,为了处理因为一些插件崩溃而导致所有插件不可用的情况,为了可以在安全模式下删除或者调整崩溃的插件。
不过这对我们研究启动过程关系不大。
DarwinLauncher.cpp
下面取自初始化部分的源码,功能是 hook posix_spawn
函数:
|
|
在更替的实现中,核心工作就是在读取环境变量 DYLD_INSERT_LIBRARIES
,向其中添加 /Library/MobileSubstrate/MobileSubstrate.dylib
,并且把这个参数放在了变量的第一位,保证其能第一个加载。
为什么是 MobileSubstrate.dylib
?
在真机的调试中进行查找:
|
|
从输出可以看到,MobileSubstrate.dylib
链接到了 SubstrateInjection.dylib
,然后后者有链接到了 SubstrateBoostrap.dylib
!
所以 hook 这个函数是为了 利用 DYLD_INSERT_LIBRARIES
将 SubstrateBoostrap.dylib
注入到程序中然后引导其他的动态库。
至此似乎就结束了,但是还有一些问题没有解决:
是谁加载的
SubstrateLauncher.dylib
?
为什么要 hookposix_spwan
?
寻找 SubstrateLauncher 的启动源头
继续查看 darwin.mk
来寻找线索,下面的一个编译目标比较引人注意:
|
|
control.tar.gz 则包含了一个 deb 安装的时候所需要的控制信息。一般有 5 个文件:control,用了记录软件标识,版本号,平台,依赖信息等数据;preinst,在解包data.tar.gz 前运行的脚本;postinst,在解包数据后运行的脚本;prerm,卸载时,在删除文件之前运行的脚本;postrm,在删除文件之后运行的脚本;在 Cydia 系统中,Cydia 的作者 Saurik 另外添加了一个脚本,extrainst_,作用与 postinst 类似。
– 摘自百度百科
由此可知,这两个是在 Cydia 被安装时做了一些工作。
extrainst_.mm
针对 arm 编译条件的跳转,观察到函数执行了这个部分:
|
|
这里看到了安装程序对启动程序作了一些处理,位置是 /etc/launchd.conf
。但在实机中并没有出现,但是相应的,我们有如下的发现:
|
|
在系统开机时,系统会自动跑这个脚本,调用了 cynject
对 SubstrateLauncher.dylib
做处理。
原来是在开机的时候作了一些动作,但是具体是什么动作,还需要继续挖掘。
cynject.cpp
以下是其源代码
|
|
代码非常简单。其中展现了这个程序的用法,第一个参数是 pid,第二个参数是动态库路径。
核心调用了 MSHookProcess
函数。那么焦点就在这了。
跳转到对应函数,该函数实现在 DarwinInjector.cpp
中。
到这里,从函数名到文件名,可以猜测,这个函数将动态库注入到了这个进程当中。
观察一下刚才那条命令:
|
|
注入的程序被硬编码为 1 ,因为这个一定是 launchd 的 pid,而且这个进程在整个系统关机是不会消亡的,保证了注入代码的常驻。
结论
现在我们可以回答我们之前的疑问了。
CyidaSubstrate
是什么时候被加载的?
CyidaSubstrate
的加载时是在程序启动时,环境变量DYLD_INSERT_LIBRARIES
被设置为 SubstrateBootrap.dylib
来保证 Loader
被加载。
CydiaSubstrate
是怎么注入到每一个 APP 中的?
这个问题题的解答就在 Cydia 入侵了 launchd
进程,替换了它的 posix_spawn
函数,因为每一个进程都诞生于 launchd
,launchd
利用 posix_spawn
中调用 fork()
来产生新的进程。而 Cydia 拦截这个入口的函数,这个时候将 SubstrateBootstrap.dylib
注入到环境中,引导组件的加载。
Extra
对于 Cydia 如何对各种东西进行 hook,之后会有下一篇文章进行探索。