iOS-逆向11-代码注入

《iOS底层原理文章汇总》

1.运行上节课的WeChat程序,ViewController中的代码不会执行,因为MachO文件中的整个都被替换了,Product目录下WeChat.app中显示包内容,提取出WeChat.app中的可执行文件WeChat

image

I.通过MachOViewer分析WeChat可执行文件,由Mach64 Header、Load Commmands、sections、Function Starts、Data in Code Entries、Symbol Table、Dynamic Symbol Table、String Table、Code Signature这几部分组成

图片.png
图片.png

可执行文件由dyld进行加载,会读取Header查看是什么样的文件,Load Command中是一些细节信息,分为四大块_PAGEZERO、_TEXT、_DATA、_LINKEDIT,告诉dyld代码段和data段分别在哪


图片.png

LCMAIN指示main函数在哪儿,LOAD COMMAND知道几乎应用程序的所有信息


image

II.代码注入思路:WeChat执行WeChat可执行文件,还需要执行WeChat.app包中的frameworks和系统的UIKit,Foundation库等,WeChat.app中依赖包含的Framework andromeda也是一个MachO文件

image

andromeda通过MachOViewer查看,没有PAGEZERO


image

微信如何告诉dyld,要加载依赖的Framework的呢?andromeda等。Load COMMANDS中有指示dyld加载andromeda的路径


image

@rpath是工程根目录Frameworks目录,dyld根据Load Commands中的相应字段去指定的目录去加载framework,
程序启动dyld会根据Load Commands中的LC_LOAD_DYLIB列表去加载frameworks,一个一个去加载动态库

III.要注入代码,可以将要注入的代码包装在动态库中,即若LC_LOAD_DYLIB列表中有要注入的代码的动态库,则动态库就会被执行

A.Framework手动注入

1.在工程中新建动态库,build后显示WeChat.app中的包内容发现CloudHook动态库已经存在于WeChat.app包中

image

image

image

image
image

此时查看MachO可执行文件中发现LC_LOAD_DYLIB列表中并没有CloudHook,也就无法执行load方法中的内容


image

可以通过Xcode查看到CloudHook是一个动态库


image

要想注入代码生效,需要修改MachO可执行文件

2.通过工具yololib修改MachO可执行文件,再次查看LC_LOAD_DYLIB列表中已经存在CloudHook了

图片.png

图片.png

将修改后的MachO可执行文件放入Wechat.ipa中替换掉原来的,重新打包
⓵.将原始的微信8.0.2.ipa包解压到当前目录下生成Payload文件夹中包含WeChat.app包


image

⓶.用修改好的MachO文件替换原始解压后的WeChat中的MachO文件


图片.png

⓷.重新打包执行zip -ry WeChata.ipa Payload/生成WeChata.ipa


image

⓸.用生成的WeChata.ipa替换到原来工程中的微信8.0.2.ipa包,重新运行,注入成功

图片.png
图片.png
image

!!!注意:若运行时程序崩溃报错如下,libsystem_c.dylib strlen,则是Framework的版本号高于手机的系统,调低Framework Target中的版本号就好了


image

图片.png

B.Dylib注入

⓵.创建空工程,将微信8.0.2.ipa包放入工程目录下的新建的APP文件夹中

image

⓶.将shell脚本文件appSign.sh放入工程根目录下
image

⓷.将appSign.sh文件放入工程Build Phases目录Run Script下
image

image

⓸.build后,工程根目录下会生成temp文件夹里面有WeChat.app包
图片.png

⓹.添加Dylib
image

图片.png

image

⓺.在CloudHook的Build settings中修改Base SDK为iOS,签名Code Signing Identity改为iOS Developer
图片.png

image

⓻.拷贝文件到Frameworks中
image

image

image

image

图片.png

⓼.通过脚本修改MachO文件,将yololib拷贝到工程根目录下
image

⓽.修改appSign.sh,build Cloud Target和Dylib注入 Target两个Target,查看libCloudHook.dylib已经拷贝到Frameworks目录中,找到libCloudHook.dylib的路径拷贝到脚本中,通过yololib修改MachO可执行文件的路径
./yololib "TARGET_APP_PATH/APP_BINARY" "Frameworks/libCloudHook.dylib",保存修改后的appSign.sh
图片.png

image

图片.png

图片.png

image

⓾.运行,查看类CloudHook中的load方法中的NSLog打印注入成功咯是否显示,通过MachOViewer查看Dylib注入.app显示包内容中的WeChat可执行文件的LC_LOAD_DYLIB列表中是否新增libCloudHook.dylib路径
图片.png

image

image

以上用脚本注入dylib成功,同理,Framework也可以通过脚本注入,只需修改脚本,指明Frameworks的路径为Frameworks/CloudHook.framework/CloudHook
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/CloudHook.framework/CloudHook"
图片.png

图片.png

图片.png

2.Method Swizzle方法交换

利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
在OC中,SEL 和 IMP 之间的关系,就好像一本书的“目录”。
SEL 是方法编号,就像“标题”一样。
IMP是方法实现的真实地址,就像“页码”一样。
他们是一一对应的关系


image

3.多种Hook方式

1.class_addMethod方式:
利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃

2.class_replaceMethod方式:
利用class_replaceMethod,直接给原始的方法替换IMP

3.method_setImplementation方式:
利用method_setImplementation,直接重新赋值原始的新的IMP

4.破坏微信注册功能

I.通过lldb调试Debug View Hierarchy,发现微信的注册的控制器WCAccountLoginControlLogic和方法onFirstViewRegister


图片.png
image

II.通过runtime修改注册方法,注册被拦截到自己的方法中

+(void)load{
    //改变微信的注册
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
    Method newMethod = class_getInstanceMethod(self, @selector(my_method));
    method_exchangeImplementations(oldMethod, newMethod);
}
-(void)my_method{
    NSLog(@"哥么注册不了了!");
}
图片.png
image

由此得知谨慎使用第三方插件,会有安全隐患,尤其是破解包
当点击登录时,若要截图密码,该怎么获取密码?


image

5.窃取登录密码调试分析

A.响应链条

通过lldb调试Debug View Hierarchy发现登录的控制器名字WCAccountMainLoginViewController和响应事件方法名字onNext,通过响应者链一层一层找到textField


image

image
(lldb) po [(WCAccountMainLoginViewController *)0x129001e00 view]
<UIView: 0x127e06580; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x1cc03fd80>>

(lldb) po [(UIView *)0x127e06580 subviews]
<__NSArrayM 0x1d4e56890>(
<WCTableView: 0x12800a200; baseClass = UITableView; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x1cc24dec0>; layer = <CALayer: 0x1cc03fde0>; contentOffset: {0, -64}; contentSize: {414, 393}; adjustedContentInset: {64, 0, 226, 0}>,
<UIView: 0x12c2a3540; frame = (124.5 694; 165 22); layer = <CALayer: 0x1d0223800>>,
<UIView: 0x127f27560; frame = (0 0; 414 64); autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x1d4430980>>
)

(lldb) po [(UIView *)0x12800a200 subviews]
<__NSArrayM 0x1c40479e0>(
<UIView: 0x12c2a75c0; frame = (0 0; 414 234); layer = <CALayer: 0x1d0221460>>,
<UIImageView: 0x127fa4cb0; frame = (3 440.667; 408 2.33333); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x1d4436380>>,
<UIView: 0x127d080c0; frame = (0 234; 414 159); layer = <CALayer: 0x1c402ddc0>>,
<UIImageView: 0x127fa50f0; frame = (408.667 56; 2.33333 387); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x1d4436520>>
)

(lldb) po [(UIView *)0x12c2a75c0 subviews]
<__NSArrayM 0x1d0252b70>(
<UIView: 0x12c2a73e0; frame = (0 0; 414 234); autoresize = LM+RM; layer = <CALayer: 0x1d02212e0>>
)

(lldb) po [(UIView *)0x12c2a73e0 subviews]
<__NSArrayM 0x1d0252b40>(
<UIView: 0x127e0ae00; frame = (414 0; 414 234); hidden = YES; layer = <CALayer: 0x1cc221100>>,
<UIView: 0x12c29b1c0; frame = (0 0; 414 234); layer = <CALayer: 0x1d043bce0>>
)

(lldb) po [(UIView *)0x12c29b1c0 subviews]
<__NSArrayM 0x1d4e55ff0>(
<CTRichTextView: 0x12c29b5c0; baseClass = UILabel; frame = (20 73; 374 33); opaque = NO; layer = <_UILabelLayer: 0x1d009e820>>,
<UIView: 0x127f68e20; frame = (0 146; 414 44); autoresize = W; layer = <CALayer: 0x1d423dd40>>,
<UIView: 0x127f8be50; frame = (0 190; 414 44); autoresize = W; layer = <CALayer: 0x1d4420260>>,
<UIView: 0x127f73ed0; frame = (20 189.667; 374 0.333333); autoresize = LM+W; layer = <CALayer: 0x1d4424aa0>>,
<UIView: 0x127f5d940; frame = (20 233.667; 374 0.333333); autoresize = LM+W; layer = <CALayer: 0x1d4424e00>>
)

(lldb) po [(UIView *)0x127f8be50 subviews]
<__NSArrayM 0x1d4e576d0>(
<WCUITextField: 0x1288d5a00; baseClass = UITextField; frame = (20 0; 384 44); text = '123456'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x1d4e57a90>; layer = <CALayer: 0x1d4420100>>
)

B.静态分析

代码注入hook登录按钮

+(void)load{
    //改变微信的注册
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
    Method newMethod = class_getInstanceMethod(self, @selector(my_method));
    method_exchangeImplementations(oldMethod, newMethod);
    //改变微信的登录
    Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
    method_exchangeImplementations(oldMethod0, newMethod0);
}
-(void)my_method{
    NSLog(@"哥么注册不了了!");
}
-(void)my_loginmethod{
    NSLog(@"哥么登录不了了!");
}

1.通过class-dump导出WeChat的头文件,class-dump工具能dump出OC所有的类,对象,对象方法,类列表,方法列表,包括成员变量,执行./class-dump -H WeChat -o ./headers/,生成headers文件夹并不是源码,是通过读取MachO文件得到的头文件,头文件并没有打包到可执行文件中

图片.png

image

!!!此时注意,官网下载的class-dump不能导出OC和Swift混编的可执行文件,需要从https://github.com/AloneMonkey/MonkeyDev/blob/master/bin/class-dump下载,否则会报错Error:Cannot find offset for address 0xd8xxxxx in stringAtAddress

2.Headers文件夹拖入Sublime Text,快速找到WCAccountMainLoginViewController.h文件,找到密码输入框WCAccountTextFieldItem *_textFieldUserPwdItem,往下找WCAccountTextFieldItem,找到WCBaseTextFieldItem中WCUITextField *m_textField

图片.png

image
image

3.onNext方法没有参数,只有self和_cmd,self表示WCAccountMainLoginViewController


图片.png

4.lldb调试通过valueForKey获取m_textField的值

(lldb) po [(WCAccountMainLoginViewController *)0x118807a00 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x1d42b0c20>

(lldb) po [(WCAccountTextFieldItem *)0x1d42b0c20 valueForKey:@"m_textField"]
<WCUITextField: 0x114241600; baseClass = UITextField; frame = (20 0; 384 44); text = 'qqwwee'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x1d504ec10>; layer = <CALayer: 0x1d463b000>>
图片.png

5.代码实现,在onNext的hook方法中通过valueForKey的方式获取密码[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]

+(void)load{
    //改变微信的注册
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
    Method newMethod = class_getInstanceMethod(self, @selector(my_method));
    method_exchangeImplementations(oldMethod, newMethod);
    //改变微信的登录
    Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
    method_exchangeImplementations(oldMethod0, newMethod0);
}
-(void)my_method{
    NSLog(@"哥么注册不了了!");
}
-(void)my_loginmethod{
    NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
}
image

5.调用回原来的逻辑,调用原来的方法,发现程序崩溃,WCAccountMainLoginViewController中找不到my_loginmethod方法,'-[WCAccountMainLoginViewController my_loginmethod]: unrecognized selector sent to instance 0x11c805400'

-(void)my_loginmethod{
    NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
    //调用回原来的逻辑
    //调用原来的方法
    [self my_loginmethod];
    //objc_msgSend()
}
图片.png

方法断点后,进入汇编分析进入方法objc_msgSend方法,查看x0和x1寄存器的值,此时WCAccountMainLoginViewController类中并没有my_loginmethod方法,方法交换后,只是改变了方法的指向,类中并没有新增方法,故报错,有三种解决办法


image

让被hook的类中包含有交换的selector,最简单的方式是新建分类,不用分类的情况下,可以通过runtime添加方法

  • I.通过给类添加方法后交换,程序能正常运行,调用原来的方法
+(void)load{
    //改变微信的登录
    //原始的Method
    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    //WC添加新方法
    class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), new_onNext,"v@:");
    //交换
    method_exchangeImplementations(onNext, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext)));
}
void new_onNext(id self,SEL _cmd){
    NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
    //调用回原来的逻辑
    //调用原来的方法
    [self performSelector:@selector(new_onNext)];
    //objc_msgSend()
}
image

图片.png
图片.png
图片.png
  • 2.通过替换原来的方法class_replaceMethod
+(void)load{
    //改变微信的登录
    //原始的Method
    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    IMP old_onNext = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
}
//原来的IMP
IMP (*old_onNext)(id self,SEL _cmd);

void new_onNext(id self,SEL _cmd){
    NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
    //调用回原来的逻辑
    //调用原来的方法
    old_onNext(self,_cmd);
    //objc_msgSend()
}
图片.png

3.通过Method的method_getImplementation和method_setImplementation方法将原始的onNext存起来后替换为新的new_onNext,后执行原始的

+(void)load{
    //改变微信的登录
    //原始的Method
    old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
    method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), new_onNext);
}
//原来的IMP
IMP (*old_onNext)(id self,SEL _cmd);

void new_onNext(id self,SEL _cmd){
    NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
    //调用回原来的逻辑
    //调用原来的方法
    old_onNext(self,_cmd);
    //objc_msgSend()
}
图片.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容