不懂汇编,如何逆向(iOS)

$ 写给像我一样的小白

0x1 逆向一个APP有哪些步骤(不越狱)

  1. 砸壳
  2. dump出头文件
  3. 分析功能界面
  4. hopper || iDA 分析伪代码
  5. 写hook
  6. 打包动态库
  7. 注入动态库到APP
  8. APP重签名
  9. 安装到手机上

MonkeyDev

MonkeyDev是一个xcode插件, 此处先膜一下@庆哥

原有iOSOpenDev的升级,非越狱插件开发集成神器!

- 可以使用Xcode开发CaptainHook Tweak、Logos Tweak 和 Command-line Tool,在越狱机器开发插件,这是原来iOSOpenDev功能的迁移和改进。
- 只需拖入一个砸壳应用,自动集成class-dump、restore-symbol、Reveal、Cycript和注入的动态库并重签名安装到非越狱机器。
- 支持调试自己编写的动态库和第三方App
- 支持通过CocoaPods第三方应用集成SDK以及非越狱插件,简单来说就是通过CocoaPods搭建了一个非越狱插件商店。

庆哥的github如是说.

MonkeyDev解决了上面说到的50%的步骤, 再外加一个动态调试.

https://github.com/AloneMonkey/MonkeyDev

PS:问问题之前先熟读wiki

0x2 剩下的工作

砸壳

其实这个是非必要项, 自己手动砸壳需要已越狱的手机. 想手动砸壳可以参考这篇文章.

iOS逆向工程之Clutch砸壳

不想自己手动砸壳的可以去各大应用平台,如PP助手等.下载越狱版的软件,这些都是已经砸好壳的了.

分析功能界面(动态分析)

主流有两种方法:

  1. Cycript
  2. Reveal

但是使用了MonkeyDev之后多了一种方法. hook oc的消息通知函数,如:ANYMethodLog.

因为每个app启动的时候都会调用appDelegate里面的didFinishLaunchingWithOptions:这个方法, 也就是说每个app都会有这个方法. 那我们可以在class-dump出来的头文件中找到某个app的appDelegate文件名,然后hook掉didFinishLaunchingWithOptions:把所有基于UIViewController或者其他类执行的方法在运行的时候全部打出来.甚至连函数的参数都可打印出来.

分析伪代码(静态分析)

上面一步分析功能界面是为了定位具体要hook的函数, 当定位出来要hook的函数之后, 就要去分析函数的具体实现了.

Hopper
iDA

这两个都是收费的,但都提供了demo版,只是做静态分析的话已经够用了.

IDA + Hopper 逆向开发近期学习

我们在这一步的目的只是为了搞清楚函数的实现和函数之间的调用关系, 所以并不需要去直接修改汇编或者二进制代码, 只是反编译出来的伪代码有可能也会带有一下寄存器或者内存地址等一些看不懂的信息,完全可以把这些当做成命名简单的变量,我们只需要看懂其中的逻辑就好了.

编写hook代码

OK, 现在要hook 的函数已经找到了,函数具体的实现也已经知道, 那下一步当然就是编写代码把函数hook掉.

captainhook

MonkeyDev中提供了logoscaptainhook两种语法,用来编写hook代码.如果原来做过越狱开发的应该比较喜欢用logos,网上的教程也比较多.但是, 我学习的时候选择用captainhook(两个都好用,纯粹个人喜好).这里简单说一下写代码的过程:

1) 如何调用已有的类和方法

如果需要使用到类的属性或类方法,最好自行创建一个头文件把@interface写进去,然后import这个头文件,写hook的时候就可以找到相应的属性了,但是如果你想通过这种方式给类添加属性是行不通的,我猜测是因为在程序运行的过程当中,内存的分配已经完成,想要添加属性值进去就需要对这个对象的内存进行扩容或者重新分配,但是通过写在自定义的头文件里面属性值,虽然是在同名的类下面,但是并不会添加在原来代码申请的内存当中,所以当你调用这个自己添加的属性的时候,原对象是找不到访问不了这个属性的,类似于Category.

如果一定要添加属性,必须实现该属性的getset方法,在里面调用runtime的外联方法objc_getAssociatedObject使对象和属性建立映射关系,这样在运行时对象才能找到你添加的属性.详细参考:Objective-C Associated Objects 的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
@interface CMessageWrap

@property(nonatomic, strong) NSString* m_nsContent; //发送消息的内容
@property(nonatomic, strong) NSString* m_nsToUsr; //发送人
@property(nonatomic, strong) NSString* m_nsFromUsr; //接收人
@property(nonatomic, strong) NSMutableString *m_nsPushContent;

+ (BOOL)isSenderFromMsgWrap:(CMessageWrap*) msgWrap;

- (CMessageWrap*)initWithMsgType:(int) type;

@end
2) CHDeclareClass
1
2
3
#define CHDeclareClass(name) \
@class name; \
static CHClassDeclaration_ name ## $;

这个宏用于声明你要hook的类.

3) CHOptimizedMethod
1
2
#define CHOptimizedMethod1(optimization, return_type, class_type, name1, type1, arg1) \
CHMethod_ ## optimization ## _(return_type, class_type *, class_type, CHClass(class_type), CHSuperClass(class_type), name1 ## $, name1:, CHDeclareSig1_(return_type, type1), (self, _cmd, arg1), type1 arg1)

CHOptimizedMethod编写你要hook的方法,这个宏后面跟着一个数字,[0-9]代表着你要hook的方法的参数个数.

4) CHSuper
1
2
#define CHSuper1(class_type, name1, val1) \
CHSuper_(class_type, @selector(name1:), name1 ## $, val1)

CHSuper用于在CHOptimizedMethod内执行完自己的代码之后继续执行原函数的代码.

5) CHConstructor
1
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()

__attribute__((constructor))后的内容能保证在 dylib 加载时运行.

6) CHLoadLateClass
1
#define CHLoadLateClass(name) CHLoadClass_(&name ## $, objc_getClass(#name))

加载需要hook的类,写在CHConstructor里面.

7) CHClassHook
1
#define CHClassHook1(class, name1) CHHook_(class, name1 ## $)

加载需要hook的方法,写在CHConstructor里面.

8) CHDeclareMethod
1
2
#define CHDeclareMethod1(return_type, class_type, name1, type1, arg1) \
CHDeclareMethod_(return_type, class_type *, class_type, CHClass(class_type), CHSuperClass(class_type), name1 ## $, name1:, CHDeclareSig1_(return_type, type1), (self, _cmd, arg1), type1 arg1)

声明和实现自己的方法, 要注意声明要写在调用之前.

9) Sample

这是我hook了微信聊天页面出现和消失两个代理方法的例子…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//聊天基本页面
CHDeclareClass(BaseMsgContentViewController)

//实现自己新加的方法
CHDeclareMethod1(void, MMUIViewController, backToMsgContentViewController, id, sender){
[sender removeFromSuperview];
UINavigationController *navi = [objc_getClass("CAppViewControllerManager") getCurrentNavigationController];
LKButton *btn = (LKButton *)sender;
[LKNewestMsgManager sharedInstance].didTouchBtnName = btn.username;
[[NSNotificationCenter defaultCenter] postNotificationName:@"btnDidTouch" object:nil];
MMServiceCenter* serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
CContactMgr *contactMgr = [serviceCenter getService:[objc_getClass("CContactMgr") class]];
CContact *contact = [contactMgr getContactByName:btn.username];

MMMsgLogicManager *logicManager = [serviceCenter getService:[objc_getClass("MMMsgLogicManager") class]];
[logicManager PushOtherBaseMsgControllerByContact:contact navigationController:navi animated:YES];

}
//hook viewDidAppear 方法
CHOptimizedMethod1(self, void, BaseMsgContentViewController, viewDidAppear, BOOL, flag){
[LKNewestMsgManager sharedInstance].currentChat = [(BaseMsgContentViewController*)[[LKNewestMsgManager sharedInstance] getCurrentVC]getCurrentChatName];

NSLog(@"%@", [LKNewestMsgManager sharedInstance].currentChat);
CHSuper1(BaseMsgContentViewController, viewDidAppear, flag);
}

//hook viewWillDisappear 方法
CHOptimizedMethod1(self, void, BaseMsgContentViewController, viewWillDisappear, BOOL, disappear){
[LKNewestMsgManager sharedInstance].currentChat = NULL;
CHSuper1(BaseMsgContentViewController, viewWillDisappear, disappear);
}

//注册需要hook的类和方法
CHConstructor{
CHLoadLateClass(BaseMsgContentViewController);
CHClassHook1(BaseMsgContentViewController, viewWillDisappear);
CHClassHook1(BaseMsgContentViewController, viewDidAppear);

}

0x3 做些有趣的事情

我平常喜欢在微信公众号看些文章, 但是这时候如果有人发消息过来, 手机震了一下…….但是你并不知道是谁发来的消息, 这时候,按照微信培养的用户习惯….置顶保存文章,然后点击n个返回按钮然后点击close返回消息页面…..回了消息然后在回去看文章…..

so,这就是痛点所在,不能快速查看回复消息.

搞起来….

功能设想

在任意页面, 当接收到异步消息, 通知当前页面弹出一个按钮提示, 点击按钮 push 对应聊天页面, pop 可返回原来的页面.

定位函数

一开始只是因为看文章的痛点,只想hook webVC页面就好了,但是后来细想,当你在弄设置或者干其他事情的时候其实也有同样的问题,干脆直接搞底层UIViewController, 但是在分析的过程中,发现微信有一个自己实现的MMUIViewController.如此甚好, 直接搞它.

通过动态分析的方法快速定位到需要hook的类:

1
2
3
BaseMsgContentViewController //基本聊天页面
MMUIViewController //VC基类
CMessageMgr //消息接收类

对于BaseMsgContentViewControllerMMUIViewController我们目的很明确,就是监听通知,当有消息来的时候,弹出按钮.

这里可能有疑问,BaseMsgContentViewController应该也是继承MMUIViewController的,为什么还要单独hook. 原因很简单,因为你在和某人的聊天页面当中,当然不应该在弹出这个人的消息按钮.

接下来就是借助class-dumpHopper去定位和分析函数, 比如,我这里需要分析的就是点击按钮之后,如何跳转到对应的聊天页面.

hook

OK, 所有需要用到的消息都拿到了, 开始写hook代码.

下面是一些关键代码,全部的代码在github:LKMessageSwitchPod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// hook 消息接收类
CHDeclareClass(CMessageMgr)
CHOptimizedMethod2(self, void, CMessageMgr, AsyncOnAddMsg, NSMutableString*, msg, MsgWrap, CMessageWrap*, wrap){

if(![wrap.m_nsPushContent isEqual: @""] && wrap.m_nsPushContent != NULL){
[LKNewestMsgManager sharedInstance].username = msg;
[LKNewestMsgManager sharedInstance].content = wrap.m_nsPushContent;
[[NSNotificationCenter defaultCenter] postNotificationName:@"LkWechatMessageNotification" object:nil];
}
CHSuper2(CMessageMgr, AsyncOnAddMsg, msg, MsgWrap, wrap);
}

CHDeclareMethod1(void, MMUIViewController, backToMsgContentViewController, id, sender){
[sender removeFromSuperview];
UINavigationController *navi = [objc_getClass("CAppViewControllerManager") getCurrentNavigationController];

LKButton *btn = (LKButton *)sender;
[LKNewestMsgManager sharedInstance].didTouchBtnName = btn.username;
[[NSNotificationCenter defaultCenter] postNotificationName:@"btnDidTouch" object:nil];
MMServiceCenter* serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
CContactMgr *contactMgr = [serviceCenter getService:[objc_getClass("CContactMgr") class]];
CContact *contact = [contactMgr getContactByName:btn.username];
MMMsgLogicManager *logicManager = [serviceCenter getService:[objc_getClass("MMMsgLogicManager") class]];
[logicManager PushOtherBaseMsgControllerByContact:contact navigationController:navi animated:YES];

}

CHDeclareMethod0(void, MMUIViewController, messageCallBack){
NSLog(@"收到消息!!!");

NSString *currentChatName = [LKNewestMsgManager sharedInstance].currentChat;
if(self == [[LKNewestMsgManager sharedInstance]getCurrentVC] && ![currentChatName isEqual: [LKNewestMsgManager sharedInstance].username]){

LKButton *btn = [LKButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(self.view.frame.size.width-100-2, 74, 100, 40);
btn.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.8];
btn.tintColor = [UIColor whiteColor];
[btn setTitle:[LKNewestMsgManager sharedInstance].content forState:UIControlStateNormal];\
btn.username = [LKNewestMsgManager sharedInstance].username;
btn.clipsToBounds = YES;
btn.layer.cornerRadius = 10;
btn.contentEdgeInsets = UIEdgeInsetsMake(2, 2, 2, 2);
[btn addTarget:self action:@selector(backToMsgContentViewController:) forControlEvents:UIControlEventTouchUpInside];
[btn registerNotification];
btn.swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipes:)];
btn.swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
btn.swipeGestureRecognizer.numberOfTouchesRequired = 1;
[btn addGestureRecognizer:btn.swipeGestureRecognizer];

[self.view addSubview:btn];
}
}

效果

1508489189.gif