iOS应用程序的脱壳实现原理浅析

应用程序加载过程

对于诸多逆向爱好者来讲,给一个app脱壳是一项必作的事情。基于安全性的考虑,苹果对上架到appstore的应用都会进行加密处理,因此若是直接逆向一个从appstore下载的应用程序时,所能看到的“源代码”将很是的晦涩难懂。为了能看懂应用程序的“源代码”,就必须对应用程序进行解密,也就是所谓的脱壳。脱壳后的目的是能够分析应用程序的一些技术实现原理,或者利用一些漏洞进行攻击和测试。ios

这篇文章不是一篇介绍如何利用工具去进行脱壳的教程,而只是简单的分析这些经常使用脱壳工具的实现原理。要想了解脱壳原理,就要先去了解一个被加密的应用程序是如何被运行的。下面一张图片简单的介绍了一个被加壳后的应用程序被加载和运行的过程:git

程序脱壳过程

脱壳原理以及常见的工具

要对一个壳应用进行脱壳处理,无非就是采用静态脱壳动态脱壳两种方法:静态脱壳就是在已经掌握和了解到了壳应用的加密算法和逻辑后在不运行壳应用程序的前提下将壳应用程序进行解密处理。静态脱壳的方法难度大,并且加密方发现应用被破解后就可能会改用更加高级和复杂的加密技术;动态脱壳就是从运行在进程内存空间中的可执行程序映像(image)入手,来将内存中的内容进行转储(dump)处理来实现脱壳处理。这种方法实现起来相对简单,且没必要关心使用的是何种加密技术。从上面的壳应用程序运行的过程就能够看出不管壳程序如何被加密处理,最终运行后在进程中的代码映像(image)始终是被解密后的原始程序二进制。因此只要一个进程内存空间中的代码映像(image)能被读取和访问就能够实现动态脱壳。下面要介绍的两个工具就是巧妙的运用了两种不一样的访问技巧来实现动态脱壳的。程序员

1、利用动态库注入来实现脱壳的dumpdecrypted/frida-ios-dump

dumpdecrypted和frida-ios-dump都是在github上开源的项目,下载地址分别为:github.com/stefanesser… 关于使用这两个工具来进行脱壳的文档很是之多。咱们知道一个应用除了有一个可执行程序外,还会连接很是多的动态库。动态库加载后和可执行程序共享相同的进程内存空间,并且动态库中的代码是能够访问整个进程内存空间中的有权限的区域的,包括可执行程序的image被加载到进程中的内存区域。所以只要想办法让应用程序加载某个特定的第三方动态库,也就是让这个第三方动态库注入到应用程序的进程中去就能够实现将被解密事后的可执行程序在进程内存中的image信息转储到文件中去从而实现脱壳处理。对于一个越狱后的设备来讲主要能够经过两种方法来实现第三方动态库的注入:github

  1. 设置环境变量DYLD_INSERT_LIBRARIES的值指向这个第三方动态库的路径。而后运行要脱壳的应用程序便可。 DYLD_INSERT_LIBRARIES环境变量的设置是一个操做系统提供的特性,全部运行的程序都会加载这个环境变量中所指向的动态库文件。算法

  2. 将第三方动态库文件保存在越狱设备的**/Library/MobileSubstrate/DynamicLibraries/**目录下并编写对应的库的同名plist文件,全部plist中指定的可执行程序一旦运行就会加载对应的动态库(此目录即Tweak插件所在的目录)。安全

还有一种直接修改对应mach-o格式的可执行文件内容来实现动态库注入。bash

动态库加载的问题解决后就须要解决动态库中代码运行的时机问题了。要想让一个被加载的动态库在加载后自动运行某一段代码能够有四种方法:app

  1. 创建一个C++全局对象,并在对象所属类的构造函数中添加特定代码。函数

  2. 创建一个OC类,并在OC类的+load方法中添加特定的代码。工具

  3. 生成动态库时指定一个初始化init入口函数,并在入口函数中添加特定的代码。

  4. 在动态库中定义一个带有_attribute_((constructor))声明的函数,并在函数内添加特定的代码。

若是你想更进一步的了解上述那些方法的加载的原理,请参考个人文章:深刻解构iOS系统下的全局对象和初始化函数

dumpdecrypted这个工具就是经过创建一个名为dumpdecrypted.dylib的动态库并在库内部定义了一个

__attribute__((constructor))
void dumptofile(int argc, const char **argv, const char **envp, const char **apple, struct ProgramVars *pvars)
复制代码

函数来实现脱壳的。这个函数的大致实现会在后面继续介绍。

2、利用父子进程关系来实现脱壳的Clutch

Clutch也是一个在github上开源的项目,下载地址为:github.com/KJCracks/Cl… 关于这个工具的使用教程也很是之多。咱们知道在unix系列的操做系统中父进程能够经过fork或者posix_spawnp两个函数来运行或者创建一个子进程的,这两个函数都会返回对应的子进程ID(PID)。iOS系统则能够经过task_for_pid函数来从进程ID获取进程在mach内核子系统中的mach port标识。获得mach port 标识后,就能够借助mach_vm_read_overwrite函数来读取指定进程空间中的任意虚拟内存区域中所存储的内容。所以Clutch内部的实现就是Clutch这个程序对将要进行脱壳的程序文件路径调用posix_spawnp函数来运行从而成为其子进程,而后借助task_for_pid以及mach_vm_read_overwrite函数来读取脱壳程序子进程在内存中已经被解密后的可执行程序的image所映射的内存空间来达到脱壳的目的的。

一个思考:可能在实际中并不必定要求是父子进程关系,是否只要某个具备特权的程序或者运行在root用户上的程序只要拿到了对应进程的PID就能够经过mach子系统提供的API来读取其余进程内存空间中的信息呢?

上述的两种方法中不论是dumpdecrypted仍是Clutch最终都是将被解密后的可执行程序的image在内存中的映射写入到一个文件中去来保存脱壳后的内容。参考dumpdecrypted中的dumptofile函数实现以及Clutch中的Dumpers目录下的实现代码就能够看出:一个可执行程序image在内存中映射的内容的结构和mach-o格式的可执行文件结构基本上是保持一致的。都是有一个mach_header结构体头还有诸多的load_command结构体组成。所以所谓的dump处理就是将内存中的这些结构和数据原封不动的写入到文件中去即完成了脱壳中的最核心的部分。若是想仔细的阅读这部分代码的实现,建议先了解一下mach-o文件格式的组成。

后记

当你了解了这些内部实现后,也许你会发觉其实它的原理很简单。并且有可能你也能很快的去实现。可问题的关键是为何这些方法老是别人能想到,而咱们却想不到呢?这是否和国人的思惟以及解决问题的方式相关呢?在咱们的教育和实践体系中更多的是拿来主义和实用主义,每每不多人会对问题进行深刻的探索研究以及进行问题的关联性思考。希望这种状况在将来可以获得改进,尤为做为一个程序员,更加应该秉持探索求知的强烈意愿而不是简单复制和应用就知足了。

最后仍是要感谢《iOS应用逆向与安全》的做者:刘培庆。向他咨询了逆向相关的一些知识后才得以写出这篇文章。并推荐逆向的爱好者阅读这本书。

相关文章
相关标签/搜索