简单代码注入

1,190 阅读4分钟

一、代码注入目的

  • 了解密码学代码/APP签名原理及重签技术后,可以对其他的应用进行重签、调试,这并不是最终目的,我们要做的是在别人的应用中添加自己的代码,并让APP执行我们的代码,这样我们才能理解如何做到恶意代码注入,并清楚知道如何去防护。
  • APP 运行时执行的文件
    • 1.系统的框架,系统库的Macho可执行文件;
      • 非越狱手机无法修改系统库的方法和实现;
      • 越狱手机可以修改(暂时做不到,以观后效);
    • 2.应用包内对应的应用的Macho可执行文件;
      • 可以直接修改Macho文件的内容(暂时不会,需要学习完汇编后直接修改其二进制文件)
    • 3.应用中添加/依赖的framework,dylib等库编译产生的Macho文件;
      • 添加一个自己的framework/dylib库,相对简单,本篇文章将着手于此,简单介绍如何通过添加framework/dylib库,打到注入代码的目的,同时也会介绍相关知识和工具;

二、代码注入原理和实现

  • 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:
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);
}