在逆向过程,我们很多时候需要动态调试把自己的代码注入目标app,替换或拦截部分功能,那是如何实现将自己代码载入目标app从而hook的呢?今天我们聊下动态库注入的过程。
本文以ofo小黄车iOS为例一步步分析动态库如何注入目标APP,首先我们通过MachoOView查看ofo的MachO结构如下:
可以知道程序默认加载了系统库Foundation、libobjc.A.dylib、libSystem.B.dylib、UIKit,还加载了React,ReactiveObjc,ofoComponents等自己的组件。
接下来我们使用MonkeyDev或IPAPatch新建自己动态库运行打包,我们再次通过MachoOView查看结构如下,可以看到我们的代码GesanInsetDylib以动态库libesanInsertDylib.dylib的形式注入了二进制文件。
通过上述过程我们大概可以看到动态库注入就是通过修改可执行文件的Load Commands,增加一个LC_LOAD_DYLIB,写入dylib路径。这样程序执行的时候就会来解析这个LC_LOAD_DYLIB找到要注入的dylib,从而进行加载。
那么动态库是怎样注入的,Mach-O文件的Load Commands是如何修改的呢?我们查看IPAPatch的patch.sh源文件可见如下脚本,注释说的很明白这里就是依赖optool实现的,庆神的MonkeyDev里pach.sh依赖monkeyparser实现注入,但和optool注入原理类似。
if [[ ${MONKEYDEV_INSERT_DYLIB} == "YES" ]];then "$MONKEYPARSER" install -c load -p "@executable_path/Frameworks/lib""$TARGET_NAME""Dylib.dylib" -t "$BUILD_APP_PATH/$APP_BINARY" "$MONKEYPARSER" unrestrict -t "$BUILD_APP_PATH/$APP_BINARY"
chmod +x "$BUILD_APP_PATH/$APP_BINARY"
fi
上边我们提到optool,本文也是以optool为例分析,题外话作者是alexzielenski,个人网站的一段自我介绍an 18 year-old independent Mac & iOS developer,2013年的时候才18岁,optool是四年前写的,大概2014年,真是强,献上膝盖。
optool is a tool which interfaces with MachO binaries in order to insert/remove load commands, strip code signatures, resign, and remove aslr. Below is its help.使用这个工具可以修改load commands,重签,移除aslr内存随机偏移。接下来我们看下源码结构如图:
我们从程序main.m的main入口函数入手看下源码分析注入原理,可以看到main函数里主要如下工作:
int main(int argc, const char * argv[]) { 1 通过XPMArgumentSignature输入参数
如 install -c <command> -p <payload> -t <target> [-o=<output>] [-b] [--resign] 2 读入Mocho可执行文件
NSData *originalData = [NSData dataWithContentsOfFile:executablePath];
headersFromBinary(headers, binary, &numHeaders);
.....
3 接下来各种Action处理
[package booleanValueForSignature:install]//注入动态库
[package booleanValueForSignature:resign] //重签
....
}
本篇只探讨动态库注入,那么我们看下action处理在install这个条件下代码如下图,可以看到调用了insertLoadEntryIntoBinary这个函数。
我们跳到operations.m文件下找到这个函数,函数入参依次为动态库路径,二进制文件数据,macho头信息,load command type类型。
BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, struct thin_header macho, uint32_t type){ 1 判断load command type 2 解析load commands查看我们要插入的dylib是否已经存在
binaryHasLoadCommandForDylib
.....
3 create a new load command
struct dylib_command command; //新建dylib_command
struct dylib dylib;
....初始化....
4 设置动态库数据
NSMutableData *commandData = [NSMutableData data];
[commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]];
....
5 将动态库插入二进制文件
[binary replaceBytesInRange:NSMakeRange(lastOffset, 0) withBytes:commandData.bytes length:commandData.length];
6 设置macho header
macho.header.ncmds += 1;//增加一个load command
macho.header.sizeofcmds += command.cmdsize;
.....
return YES;
}
到这里整个动态库注入过程我们已经完成,接下来有符号表恢复,重签相关代码感兴趣的可自行阅读。
最后这里简单说下动态库,动态库在编译时并不联入二进制代码,而是在程序启动后运行时通过load command载入内存,动态库载入流程怎样的?程序启动main函数执行前程序已经执行了哪些代码?像ofo采用pod管理依赖包生成很多动态库在启动时载入会不会造成程序启动慢?那采用carthage会不会变快?等等这些一系列的问题我们在后边的文章中讨论。
附相关资料 :
https://github.com/alexzielenski/optool
Tips:需要安全相关电子书的ioser可以在本公众号后台留下邮箱
更多骚操作,尽在iOSTips,关注公众号,第一时间get新姿势。