一、代码注入目的
- 了解密码学,代码/APP签名原理及重签技术后,可以对其他的应用进行重签、调试,这并不是最终目的,我们要做的是在别人的应用中添加自己的代码,并让APP执行我们的代码,这样我们才能理解如何做到恶意代码注入,并清楚知道如何去防护。
- APP 运行时执行的文件
- 1.系统的框架,系统库的Macho可执行文件;
- 非越狱手机无法修改系统库的方法和实现;
- 越狱手机可以修改(暂时做不到,以观后效);
- 2.应用包内对应的应用的Macho可执行文件;
- 可以直接修改Macho文件的内容(暂时不会,需要学习完汇编后直接修改其二进制文件)
- 3.应用中添加/依赖的framework,dylib等库编译产生的Macho文件;
- 添加一个自己的framework/dylib库,相对简单,本篇文章将着手于此,简单介绍如何通过添加framework/dylib库,打到注入代码的目的,同时也会介绍相关知识和工具;
- 1.系统的框架,系统库的Macho可执行文件;
二、代码注入原理和实现
-
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统读取程序MachO文件的Header段信息,做好程序准备工作之后,交由dyld负责加载动态库和静态库以及其他工作(后面章节会详细介绍)。
-
动态库:链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。
-
静态库:链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。
-
一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用FrameWork或者Dylib等三方库的方式注入;
-
1、FrameWork 注入:
- 1.重签后,通过Xcode新建Framwork,将库安装进入APP包,但是程序运行时不会调用执行我们FrameWork库内的代码;
- 2.将自己的FrameWork添加到MachO文件中的loadCommands中,需要用工具yololib,将自己写的FrameWork编译后产生的MachO文件写入APP的MachO文件的loadCommands中,通过yololib注入Framwork库路径;
- 命令:$yololib(空格)MachO文件路径(空格)库路径
- 所有的Framwork加载都是由DYLD加载进入内存被执行的
- 注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB字段中
-
2、Dylib注入
-
通过Xcode新建Dylib库(注意:Dylib属于MacOS所以需要修改属性)
- 1.修改Base SDK 为iOS;
- 2.签名信息修改为iOS Developer
-
添加Target依赖,让Xcode将自定义Dylib文件打包进入APP包。
- 1.项目target的Build Phases 添加Copy Files 依赖库,依赖库中依赖刚新建的dylib库
-
利用yololib进行注入。
-
三、利用 RunTime 进行代码注入,交换原来的实现
-
1、Method Swizzle
- 利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。

- 正向开发时,一般写在相应类的分类中,主要API:
- 利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
1.动态添加方法
///添加方法
class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
2.交换方法
///获取实例方法
class_getInstanceMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>)
///获取类方法
class_getClassMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>)
///交换方法
method_exchangeImplementations(<#Method _Nonnull m1#>, <#Method _Nonnull m2#>)
3.替换方法实现
/// 替换方法实现
class_replaceMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
4.设置/获取方法实现
/// 设置方法实现
method_setImplementation(<#Method _Nonnull m#>, <#IMP _Nonnull imp#>)
/// 获取方法实现
method_getImplementation(<#Method _Nonnull m#>)
-
2、Hook的内容
- 1.重签后运行到手机上的应用,断点调试,找到相应的类名和方法名;
- 2.利用class-dump将MachO文件内的所有类的描述拷贝出来,即所有.h头文件;
- 命令:class-dump -H 文件名 -o 输出文件的路径;eg:$class-dump -H WeChat -o ./headers/
- 3.在LLDB中通过 valueForKey: 获取相应UI控件;
-
3、多种Hook方式
-
1.method_exchangeImplementations 方法交换的方式不行
- oc方法默认参数self是方法的调用者,_cmd方法编号;
- 无法获取当前类,就无法写成分类;
- 写在frameWork中的代码,交换时原类或其对象无法调用当前方法;
-
+ (void)load{
///1、方法交换 找不到方法报错
Method oldMethod = class_getInstanceMethod(objc_getClass("JDNewLoginViewController"), @selector(loginAction:));
Method newMethod = class_getInstanceMethod(self, @selector(nasy_loginAction:));
method_exchangeImplementations(oldMethod, newMethod);
}
/// 1、方法交换
-(void)nasy_loginAction:(id)btn {
NSLog(@"\n\n\nusername == %@......\npassword == %@......\n\n\n\n",[[[self valueForKey:@"_inputView"] subviews][0] valueForKey:@"text"],[[[self valueForKey:@"_inputView"] subviews][2] valueForKey:@"text"]);
[self nasy_loginAction:nil];
}
-
-
2.class_addMethod方式:
- 利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃
-
///2、添加方法
+ (void)load{
Method oldMethod = class_getInstanceMethod(objc_getClass("JDNewLoginViewController"), @selector(loginAction:));
BOOL isAdd = class_addMethod(objc_getClass("JDNewLoginViewController"), @selector(nasy_loginAction:), new_loginAction, "v@:");
if (isAdd) {
Method newMethod = class_getInstanceMethod(objc_getClass("JDNewLoginViewController"), @selector(nasy_loginAction:));
method_exchangeImplementations(oldMethod, newMethod);
}
}
void new_loginAction(id self , SEL _cmd){
NSLog(@"\n\n\nusername == %@......\npassword == %@......\n\n\n\n",[[[self valueForKey:@"_inputView"] subviews][0] valueForKey:@"text"],[[[self valueForKey:@"_inputView"] subviews][2] valueForKey:@"text"]);
[self performSelector:@selector(nasy_loginAction:) withObject:nil];
}
-
-
3.class_replaceMethod方式:
- 利用class_replaceMethod,直接给原始的方法替换IMP
-
/// 3、交换方法实现
+ (void)load{
oldIMP = class_getMethodImplementation(objc_getClass("JDNewLoginViewController"), @selector(loginAction:));
class_replaceMethod(objc_getClass("JDNewLoginViewController"), @selector(loginAction:), new_loginAction, "v@:");
}
IMP ( * oldIMP)(id self,SEL _cmd);
void new_loginAction(id self , SEL _cmd){
NSLog(@"\n\n\nusername == %@......\npassword == %@......\n\n\n\n",[[[self valueForKey:@"_inputView"] subviews][0] valueForKey:@"text"],[[[self valueForKey:@"_inputView"] subviews][2] valueForKey:@"text"]);
oldIMP(self,_cmd);
}
-
-
4.method_setImplementation方式:
- 利用method_setImplementation,直接重新赋值原始的新的IMP
-
/// 4、set get IMP
+ (void)load{
oldIMP = class_getMethodImplementation(objc_getClass("JDNewLoginViewController"), @selector(loginAction:));
Method oldMethod = class_getInstanceMethod(objc_getClass("JDNewLoginViewController"), @selector(loginAction:));
method_setImplementation(oldMethod, new_loginAction);
}
IMP ( * oldIMP)(id self,SEL _cmd);
void new_loginAction(id self , SEL _cmd){
NSLog(@"\n\n\nusername == %@......\npassword == %@......\n\n\n\n",[[[self valueForKey:@"_inputView"] subviews][0] valueForKey:@"text"],[[[self valueForKey:@"_inputView"] subviews][2] valueForKey:@"text"]);
oldIMP(self,_cmd);
}