iOS逆向实战--015:代码注入

一般修改原始程序,会利用代码注入的方式,注入代码就会选择利用FrameWorkdylib等三方库的方式注入。

注入原理

当运行重签名的App时,想让它触发当前项目中的代码,例如:写在ViewController的中代码,这是不可能的。因为项目中的App在安装时会被重签名App替换掉,它们根本不是一个MachO文件。

代码注入原理:

使用MachOView分析可执行文件

wx8.0.2.ipa中导出WeChat可执行文件,拖入MachOView,可能会出现没有权限的错误提示

解决方法,使用MachOView菜单中的File -> Open...

选择WeChat可执行文件

成功解析WeChat中的内容

dyld加载可执行文件时,先读取MachO中的Header,获取MachO类型。然后读取Load Commands,通过读取__PAGEZERO__TEXT__DATA__LINKEDIT等段,可以得到MachO的大小,代码段和数据段的位置,告知dyld应该如何将MachO加载到内存中。当dyld读取代码段时,通过读取LC_MAIN找到程序入口。所以读取Load Commands几乎可以读取到MachO的所有信息

dyld除了加载MachO,还要加载UIkitFoundation等系统库,以及Frameworks目录下的动态库

Load Commands中,列出MachO所依赖的所有系统库以及三方库

所以dyld会加载Load Commands中包含的所有库。如果将注入的代码包装成一个动态库,将其插入到Load Commands中,理论上动态库可以被加载,注入的代码也可以被执行

Framework注入

Framwork注入流程:

  • 通过Xcode新建Framwork,将库安装进入App
  • 通过yololib注入Framwork库路径
./yololib MachO文件路径 库路径
  • 所有的Framwork加载都是由dyld加载进入内存被执行的
  • 注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB字段中

案例1:

使用Framework注入代码

打开自动重签名WeChat项目,创建Framework动态库

命名HOOK

动态库中创建Class,命名Inject

打开Inject.m文件,写入以下代码:

#import "Inject.h"

@implementation Inject

+(void)load{
   NSLog(@"\n\n\n\n\n🍺🍺🍺🍺🍺\n\n\n\n\n");
}

@end

编译项目,在App包中的Frameworks目录,可以找到HOOK.framework动态库

此时HOOK动态库中的代码还不能被执行,因为MachOLoad Commands中,还没有插入HOOK动态库的LC_LOAD_DYLIB字段

使用yololibMachO注入动态库

MachO文件,拷贝到yololib文件的同级目录

注入动态库

./yololib WeChat Frameworks/HOOK.framework/HOOK
-------------------------
2021-04-22 17:14:52.339 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK
2021-04-22 17:14:52.340 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK
Reading binary: WeChat

2021-04-22 17:14:52.341 yololib[22774:33058817] Thin 64bit binary!
2021-04-22 17:14:52.341 yololib[22774:33058817] dylib size wow 72
2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 124
2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 125
2021-04-22 17:14:52.341 yololib[22774:33058817] Patching mach_header..
2021-04-22 17:14:52.399 yololib[22774:33058817] Attaching dylib..

2021-04-22 17:14:52.399 yololib[22774:33058817] size 71
2021-04-22 17:14:52.399 yololib[22774:33058817] complete!

查看MachOLoad Commands,成功将HOOK动态库插入到最后,路径是注入时设置的参数2

解压缩wx8.0.2.ipa,将MachO文件替换,然后重新打包ipa

将重新打包的wx8.0.2.ipa,拷贝到项目中APP目录

真机运行项目,App安装成功,正常运行,同时打印注入代码

dylib注入

dylib注入流程:

  • 通过Xcode新建dylib库(注意:dylib属于macOS,所以需要修改属性)
  • 添加Target依赖,让Xcode将自定义dylib文件打包进入App
  • 利用yololib进行注入

案例1:

使用dylib注入代码

打开自动重签名WeChat项目,创建dylib动态库

命名HOOK

Build Settings中,将Base SDK设置项修改为iOS

Code Signing Identity设置项修改为iOS Developer

切换到AppTarget,选择Build Phases,点击+,选择New Copy Files Phase

Copy FilesDestination中,选择Frameworks

点击+,选择libHOOK.dylib

打开HOOK.m文件,写入以下代码

#import "HOOK.h"

@implementation HOOK

+(void)load{
  NSLog(@"\n\n\n\n\n🍺🍺 dylib 🍺🍺\n\n\n\n\n");
}

@end

使用脚本注入动态库:将yololib文件,拷贝到项目根目录

打开rsign.sh文件,加入以下代码:

./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libHOOK.dylib"

真机运行项目,App安装成功,正常运行,同时打印注入代码

Method Swizzle

利用OCRuntime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。

OC中,SELIMP之间的关系,就好像一本书的“目录

  • SEL是方法编号,就像“标题”一样
  • IMP是方法实现的真实地址,就像“页码”一样
  • 它们是一一对应的关系

Runtime提供了交换两个SELIMP对应关系的函数

OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
   OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

通过这个函数交换两个SELIMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)

多种HOOK方式

  • class_addMethod方式:让原始方法可以被调用,不至于因为找不到SEL而崩溃
  • class_replaceMethod方式:直接给原始的方法替换IMP
  • method_setImplementation方式:直接重新赋值新的IMP
窃取登录密码

案例1:

点击登录按钮,输出用户输入的密码

延用Framwork代码注入的案例

打开rsign.sh文件,加入以下代码:

./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HOOK.framework/HOOK"

真机运行项目,进入登录页,使用Debug View Hierarchy动态调试

找到登录按钮的TargetAction

  • TargetWCAccountMainLoginViewController
  • ActiononNext

想要打印密码框的内容,必须先找到密码框的位置,然后通过控件的属性拿到内容

【方式一】:动态调试

找到密码框的位置,找到存储内容的text属性

  • 可以通过响应链条,根据左侧树状图结构,对ViewController.view.subviews进行递归遍历,找到符合条件的WCUITextField为止

使用动态调试,虽然也能找到密码框的位置,但寻找的过程太过繁琐,不推荐使用

【方式二】:静态分析

目前通过动态分析,可以确定登录按钮和密码框都在WCAccountMainLoginViewController

使用class-dump工具,通过MachO导出OC中所有类和方法列表以及成员变量

MachO文件,拷贝到class-dump文件的同级目录

导出OC中所有类和方法列表

./class-dump -H WeChat -o ./headers/
-------------------------
2021-04-25 13:51:24.070 class-dump[31659:33427860] Warning: Parsing instance variable type failed, ready_
2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, underlying
2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, enable
...
2021-04-25 13:52:09.288 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList:
2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList:
2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getExtensionListForSelector:

打开headers目录,列出所有导出的文件列表,找到WCAccountMainLoginViewController文件

打开WCAccountMainLoginViewController文件,没有找到WCUITextField控件,但发现一个WCAccountTextFieldItem类型控件,名称为_textFieldUserPwdItem

打开WCAccountTextFieldItem文件,还是没有找到WCUITextField控件,但它继承自WCBaseTextFieldItem类,我们要找的控件很有可能在父类中

打开WCBaseTextFieldItem文件,找到WCUITextField控件

使用代码之前,可以先结合动态调试,验证能否顺利获取到用户密码

通过动态调试,成功获取密码框里的内容

使用代码,获取用户密码

打开Inject文件,写入以下代码:

#import "Inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

@implementation Inject

+(void)load{
   Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
   Method my_onNext = class_getInstanceMethod(self, @selector(hook_onNext));
   method_exchangeImplementations(wx_onNext, my_onNext);
}

-(void)hook_onNext{
   UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
   NSLog(@"密码:%@", txtPwd.text);
}

@end

真机运行项目,点击登录按钮

密码:123456

成功输出用户密码

案例2:

窃取密码后,希望可以调用原始的代码逻辑

日常开发中,方法交互一般写在当前类的分类中。由于交互后hook_onNext指向了onNextIMP,所以代码中直接调用hook_onNext即可

打开Inject文件,修改hook_onNext方法:

-(void)hook_onNext{
   UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
   NSLog(@"密码:%@", txtPwd.text);
   
   [self hook_onNext];
}

但是,HOOK动态库中的Inject文件,和WCAccountMainLoginViewController类没有任何关联。虽然hook_onNext指向onNextIMP,但是调用objc_msgSend函数时,给VC发送hook_onNext,程序会因为找不到SEL而崩溃

下面介绍三种方式,都可以解决上述问题

【方式一】:class_addMethod

既然VC中没有hook_onNext方法,可以使用class_addMethod函数给VC添加一个hook_onNext,调用时不会因为找不到SEL而崩溃

打开Inject文件,写入以下代码:

#import "Inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

@implementation Inject

+(void)load{
   Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
   
   class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext), hook_onNext, @"v@:");
   Method my_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext));
   
   method_exchangeImplementations(wx_onNext, my_onNext);
}

void hook_onNext(id self, SEL _cmd){
   UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
   NSLog(@"密码:%@", txtPwd.text);
   
   [self performSelector:@selector(hook_onNext)];
}

@end
  • 之前的hook_onNext方法,改为函数实现,增加OC方法的两个隐式参数
  • 使用class_addMethod函数,给VC增加hook_onNext方法,函数名即是IMP
  • VC下的hook_onNextonNext进行方法交互
  • hook_onNext函数中,如果直接调用函数,调用的不是替换后的IMP,而是hook_onNextIMP,这样会形成递归。所以想调用原始方法,要使用objc_msgSendperformSelector方式调用

真机运行项目,点击登录按钮,窃取密码后,调用原始的代码逻辑

密码:123456

【方式二】:class_replaceMethod

VConNext方法的IMP替换

打开Inject文件,写入以下代码:

#import "Inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

@implementation Inject

IMP (*oldImp)(id self, SEL _cmd);

+(void)load{
   oldImp = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), hook_onNext, @"v@:");
}

void hook_onNext(id self, SEL _cmd){
   UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
   NSLog(@"密码:%@", txtPwd.text);
   
   oldImp(self, _cmd);
}

@end
  • 声明IMP类型的oldImp函数指针
  • 使用class_replaceMethod函数,直接将onNextIMP替换为hook_onNextIMP,将返回的原始IMP赋值给oldImp
  • hook_onNext函数中,想调用原始方法,直接调用oldImp并传入隐式参数即可

【方式三】:method_setImplementation

获取原始的IMP,重新赋值新IMP

打开Inject文件,写入以下代码:

#import "Inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

@implementation Inject

IMP (*oldImp)(id self, SEL _cmd);

+(void)load{
   Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
   oldImp = method_getImplementation(wx_onNext);
   
   method_setImplementation(wx_onNext, hook_onNext);
}

void hook_onNext(id self, SEL _cmd){
   UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
   NSLog(@"密码:%@", txtPwd.text);
   
   oldImp(self, _cmd);
}

@end
  • 使用method_getImplementation函数,获取onNextIMP,赋值给oldImp
  • 使用method_setImplementation函数,直接赋值hook_onNextIMP
总结

注入原理:

  • Xcode将注入的动态库打包进App包中
  • MachOLoad Commands中,包含注入动态库的LC_LOAD_DYLIB字段
  • DYLD加载注入的动态库

注入方式:

  • Framework注入
  • dylib注入

Framwork注入流程:

  • 通过Xcode新建Framwork,将库安装进入App
  • 通过yololib注入Framwork库路径

dylib注入流程:

  • 通过Xcode新建dylib库(注意:dylib属于macOS,所以需要修改属性)
  • 添加Target依赖,让Xcode将自定义dylib文件打包进入App
  • 利用yololib进行注入

案例,窃取登录密码:

  • 分析思路
  • 动态调试,界面入手
  • 静态分析,使用class-dump,通过MachO导出OC的类和方法列表

Method Swizzle

  • 使用method_exchangeImplementations交互SELIMP对应关系,此方式存在隐患,如果不在同一个类,无法调用原始方法,因为找不到SEL而崩溃
  • 使用class_addMethod添加方法,不至于因为找不到SEL而崩溃。过程比较复杂,不推荐使用
  • 使用class_replaceMethod替换SELIMP
  • 使用method_getImplementationmethod_setImplementation配合,直接重新赋值新的IMP。逻辑清晰,大部分HOOK框架使用此方式,推荐使用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 本文涉及内容无风险,但某信有检测BundId机制,建议不要大号登录 本文是建立在应用重签名的基础上 iOS逆向 应...
    Lee坚武阅读 1,959评论 2 2
  • 代码注入 一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用FrameWork或者Dylib等三方库的...
    king_jensen阅读 1,346评论 2 5
  • 一、代码注入 重签名app后自己的壳工程的代码就被替换掉了(替换了整个MachO),并不会执行。iOS系统是通过d...
    HotPotCat阅读 1,315评论 0 3
  • 利用LLDB对微信进行分析,然后利用分析的结果,再逐步讲解如何Hook微信的登录过程,截获微信密码。 在上一篇文章...
    一缕清风扬万里阅读 7,587评论 46 45
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53