IDA + Hopper 逆向开发近期学习

0.055字数 3163阅读 2417

前言

近期实战了一次 IDA + Hopper 逆向破解。讲真,第一次体验了一回把别人“衣服”扒光了的快感~简直 High 翻~所以,特此,利用 AlipayWallet 总结分享一下 IDA 和 Hopper 的基本使用。希望对大家有帮助。

先回顾一下,之前两篇文章已经学习的内容:

获得一台越狱设备

利用 SSH 连接访问越狱设备

利用 Clutch 解密砸壳

利用 class-dump 导出应用头文件

利用 Cycript 进行应用运行时的动态分析与修改

这么一看确实学了不少技能,而接下来这篇文章,会简单介绍两个反编译的利器 IDA 和 Hopper 的使用。

注,本次实验利用的并不是最新版本的支付宝 Mach-O 文件,因此可能本次实验中涉及的函数方法在最新版本中无法找到,但由于本文章只作为学习,所以,只提供具体的方法。如果有需要,可自行砸壳获得。

动态分析与静态分析

动态分析

我们前两篇学习的 Cycript ,就是典型的动态分析工具。Cycript 可以在应用进行运行时方法分析,视图层级分析等操作。而动态分析除了 Cycript 以外,常用的工具还有 lldb & debugserver 远程断点调试,logify 追踪等,以后一起慢慢学习这些技能方法。

静态分析

Static program analysis is the analysis of computer software that is performed without actually executing programs (analysis performed on executing programs is known as dynamic analysis).[1] In most cases the analysis is performed on some version of the source code, and in the other cases, some form of the object code.

以上是 Wikipedia 对静态分析的英文介绍,中文翻译如下。

静态程序分析是指,没有实际执行程序的电脑软件分析方法(与之相对的是,被人们所知的实际执行软件的动态分析)。在绝大部分的例子中,分析是运行在源代码的某些版本,而其余则是运行在目标代码中。

之前学习的 class-dump 工具导出 Mach-O 头文件,就是对软件应用进行静态分析。而今天我们要学习的两个工具,IDA 和 Hopper 反汇编二进制文件同样也是静态分析的方法。

IDA 的基本使用

什么是 IDA

IDA is the Interactive DisAssembler: the world’s smartest and most feature-full disassembler, which many software security specialists are familiar with.

IDA 是世界上最敏捷和多功能的反编译工具,被众多软件安全专家所熟知的交互的反汇编工具。

安装 IDA

IDA 的官方网站是https://www.hex-rays.com/。在网页上可以看到 IDA 的插件、SDK 等内容。IDA 会在官网提供 Demo 版的下载https://www.hex-rays.com/products/ida/support/download_demo.shtml。然而, Demo 版没有 IDA 最强大的反汇编功能,神器 F5~!如果自己学习使用正版,很难承担巨额的证书费用,所以一般情况是在网上找破解版(囧。。并不推荐,土豪还是建议买证书)。很不幸的是 IDA Pro 的 Mac 版非常难找(我是没找到)。所以,本文是在 Windows 下找破解 IDA Pro 来学习。

IDA Pro 的使用

打开 IDA 会出现AboutSupport message等提示对话框,点击OK继续后弹出Quick Start对话框,在这个对话框里可以看到之前打开过的 IDA 反汇编二进制文件,也可以新建一个新的反汇编文件。这里我们点击New新建一个反汇编,这次以支付宝为例。


准备 Mach-O

回顾之前的内容,先进行 Clutch 砸壳,然后 scp AlipayWallet 到 Mac 目录下,再利用 class-dump 导出头文件。

导入 IDA

这里可以直接将 Mach-O 文件拖拽进 IDA 的工作区,也可以点击打开文件夹的图标将 Mach-O 导入。检测到 Objective-C 2.0 代码时会提示,点击OK继续即可。

之后就是漫长的等待~~~(Hopper 相对于 IDA 等待的时间会短一些,但是反编译结果没有 IDA 更接近真实的 C 语言代码,会包含很多寄存器变量)

认识 IDA 工作区

当 IDA 对 Mach-O 解析完成后,会默认出现 6 个选项卡视图,分别是汇编视图,16 进制字节视图,结构体视图,枚举类型视图,导入的函数视图,导出的函数视图

另外,红框标注的是工具栏,蓝框标注的是二进制文件解析的进度,在蓝框下面,用不同颜色区分库函数、数据、常规函数等不同类型的解析数据。


静态分析:还原源代码

使用 IDA 反汇编二进制文件的目的是,利用工具得到反汇编之后的伪代码,还原出真正的程序源码。比如,我们如果要看一下登录方法是如何写的,可以在已经导出的 AlipayWallet.h 中查询login关键字,并查找相关代码,我们发现会有LoginProtocol协议和LoginAdapter类的相关代码,那我们不妨就拿LoginAdapter这个类作为实战研究对象。

@protocol LoginProtocol NSObject>

+ (id)sharedInstantce;

- (NSDictionary *)currentSession;

- (void)loginWithLoginOption:(int)arg1 extraInfo:(NSDictionary *)arg2 completionHandler:(void (^)(BOOL, NSDictionary *))arg3 cancelationHandler:(void (^)(void))arg4;

- (void)loginWithLoginOption:(int)arg1 completionHandler:(void (^)(BOOL, NSDictionary *))arg2 cancelationHandler:(void (^)(void))arg3;

- (BOOL)isValidLogin;

@optional

- (void)logout;

- (void)markInvalidLogin;

- (BOOL)isProcessingLogin;

@end

@interface LoginAdapter : NSObject LoginProtocol>

{

int _is_login_doing;

int _is_processing_pending;

id _network_config;

id _login_service;

NSMutableArray *_loginPendingRequests;

NSRecursiveLock *_pending_lock;

}

+ (id)sharedInstantce;

@property(retain, nonatomic) NSRecursiveLock *pending_lock; // @synthesize pending_lock=_pending_lock;

@property(retain, nonatomic) NSMutableArray *loginPendingRequests; // @synthesize loginPendingRequests=_loginPendingRequests;

@property(retain, nonatomic) id login_service; // @synthesize login_service=_login_service;

@property(retain, nonatomic) id network_config; // @synthesize network_config=_network_config;

- (void).cxx_destruct;

- (void)storeSessionWithLoginResult:(id)arg1;

- (id)currentUserId;

- (void)notifyNetworkSDK:(id)arg1;

- (void)logout:(id)arg1;

- (void)logined:(id)arg1;

- (void)loadAlu:(Class)arg1;

- (void)loadLoginModule;

- (void)loadNetworSDKConfig;

- (void)failedPendingLoginRequests;

- (void)redoPendingLoginRequests;

- (void)pendingLoginRequest:(id)arg1;

- (void)releasePendingLock;

- (void)accquirePendingLock;

- (void)releaseLoginLock;

- (BOOL)accquireLoginLock;

- (int)tryLogin:(id)arg1 isForce:(BOOL)arg2;

- (int)tryLogin:(id)arg1;

- (void)logout;

- (id)currentSession;

- (int)loginWithLoginOption:(int)arg1 isForce:(BOOL)arg2 extraInfo:(id)arg3 completionHandler:(CDUnknownBlockType)arg4 cancelationHandler:(CDUnknownBlockType)arg5 request:(id)arg6;

- (void)loginWithLoginOption:(int)arg1 extraInfo:(id)arg2 completionHandler:(CDUnknownBlockType)arg3 cancelationHandler:(CDUnknownBlockType)arg4;

- (void)loginWithLoginOption:(int)arg1 completionHandler:(CDUnknownBlockType)arg2 cancelationHandler:(CDUnknownBlockType)arg3;

- (BOOL)isValidLogin;

- (BOOL)isProcessingLogin;

- (void)markInvalidLogin;

- (id)setCustomLoginModule:(id)arg1;

- (void)dealloc;

- (id)init;

// Remaining properties

@property(readonly, copy) NSString *debugDescription;

@property(readonly, copy) NSString *description;

@property(readonly) unsigned int hash;

@property(readonly) Class superclass;

@end

在头文件里查找可以得到上面结果,以LoginAdapter为例。我们可以在 function 窗口中按住 Ctrl+F 键查找LoginAdapter的相关函数。


双击[LoginAdapter sharedInstan]到达这个函数在二进制文件中的内存地址,按F5可以就查看这个反编译的伪码。

这里可以看一下登录方法 Alipay 是如何写的,双击[LoginAdapter loginWithLoginOption:isForce:extraInfo:completionHandler:cancelationHandler:request:],按F5键查看这个方法的反编译伪代码。伪代码如下。

#import"LoginAdapter.h

#import"LogAdapter.h"

#import"MtopExtRequest.h"

@implementation LoginAdapter

- (NSInteger)loginWithLoginOption:(int)option

isForce:(BOOL)isForce

extraInfo:(NSDictionary *)extraInfo

completionHandler:(AlipayCompletionHandler)completionHandler

cancelationHandler:(AlipayCancelationHandler)cancelationHandler

request:(MtopExtRequest *)request {

// self是当前实例指针,a2对应方法选择器,a3对应方法参数option; a4对应方法参数isForce; a5对应方法参数extraInfo;

// a6对应方法参数completionHandler; a7对应方法参数cancelationHandler; a8对应方法参数request

NSInteger returnValue =0;// v18

if(!self.login_service) {

returnValue =0;

}

if(request) {

[self accquirePendingLock];

}

if([self accquireLoginLock]) {

if(extraInfo || ![self isValidLogin]) {

if(request) {

[self pendingLoginRequest:request];

}

LogAdapter *logAdapter = [LogAdapter getInstance];

NSString *apiName = [request getApiName];

NSString *apiVersion = [request getApiVersion];

NSString *logString = [NSString stringWithFormat:@"[LoginAdapter] apiName: %@, apiVersion: %@ pull login module", apiName, apiVersion];

[logAdapter warn:logString];

[self.login_service loginWithLoginOption:option

extraInfo:extraInfo

completionHandler:completionHandler

cancelationHandler:cancelationHandler];

}// extraInfo || ![self isValidLogin] end

[self releaseLoginLock];

if(completionHandler) {

NSDictionary *currentSession = [self.login_service currentSession];

completionHandler(1, currentSession);

}else{

// v38 = v8即,对self进行retain操作,无需翻译成oc代码

}

returnValue =3;

}else{

if(request) {

if(self.loginPendingRequests.count) {

returnValue =0;

}else{

[self pendingLoginRequest:request];

returnValue =1;

}

}else{

returnValue =0;

}

if(completionHandler) {

completionHandler(0, [NSDictionary dictionary]);

}else{

completionHandler = nil;

}

}

if(request) {

[self releaseLoginLock];

}

returnreturnValue;

}

@end


Hopper 的基本使用

对于 Hopper 的学习,依然使用 AlipayWallet 这个文件进行分析,与 IDA 做比较,类比进行。

什么是 Hopper

Hopper Disassembler, the reverse engineering tool that lets you disassemble, decompile and debug your applications.

Hopper 反汇编工具,是逆向工程的工具,能够让你进行反汇编、反编译并且调试你的应用。

安装 Hopper

Hopper 的官网是https://www.hopperapp.com/,与 IDA 相同,官网也提供了 Demo 的试用版。试用版也可以进行反汇编,但是不能保存 *.hop 文件。所以,用于学习的话 Hopper 的 Demo 版就足够了。

Hopper 的使用

导入 Hopper

与 IDA 不同, Hopper 不能直接将 Mach-O 文件导入工作区,需要下载应用的 *.ipa 文件,并将文件后缀改为 *.zip,解压该 zip 文件后就可看到 Payload 文件目录,在该目录下就存有对应应用的 package 包文件。右击该 package 出现菜单,选择Show Package Contents选项,就可看到包内容。


认识 Hopper 工作区

与 IDA 类似, Hopper 也有二进制文件的解析进度条,左边是符号 Label 等区域,中间是 ARM 的汇编代码,右边是相关信息栏。


静态分析:Hopper 反汇编的伪代码

如图,在最左列的搜索栏中输入loginWithLoginOption关键字,双击选择[LoginAdapter loginWithLoginOption:isForce:extraInfo:completionHandler:cancelationHandler:request:]方法,在中间的汇编代码区域,光标就会跳到该方法的内存地址处。

当 Hopper 分析完全部二进制后,按住Option + Enter键,就可看到 Hopper 反编译后的伪代码,如下所示。

int -[LoginAdapter loginWithLoginOption:isForce:extraInfo:completionHandler:cancelationHandler:request:](void * self, void * _cmd, int arg2, char arg3, void * arg4, void * arg5, void * arg6, void * arg7) {

stack[2048] = arg4;

r7 = (sp - 0x14) + 0xc;

sp = sp - 0x74;

r8 = self;

r5 = arg3;

stack[2051] = arg2;

stack[2053] = [arg4 retain];

r6 = [arg5 retain];

r11 = [arg6 retain];

r10 = [arg7 retain];

if (r8->_login_service == 0x0) goto loc_2aaee0a;

loc_2aaed6c:

stack[2054] = r6;

if (r10 != 0x0) {

[r8 accquirePendingLock];

}

if (([r8 accquireLoginLock] & 0xff) == 0x0) goto loc_2aaefa8;

loc_2aaeda0:

if (((r5 & 0xff) != 0x0) || (([r8 isValidLogin] & 0xff) == 0x0)) goto loc_2aaee10;

loc_2aaedbe:

[r8 releaseLoginLock];

r6 = stack[2054];

if (r6 != 0x0) {

stack[2052] = r8;

r5 = [[r8->_login_service currentSession] retain];

(*(r6 + 0xc))(r6, 0x1, r5, *(r6 + 0xc));

[r5 release];

}

else {

stack[2052] = r8;

}

r5 = 0x3;

goto loc_2aaf044;

loc_2aaf044:

r4 = stack[2053];

goto loc_2aaf046;

loc_2aaf046:

if (r10 != 0x0) {

[stack[2052] releasePendingLock];

}

r6 = stack[2054];

goto loc_2aaf060;

loc_2aaf060:

[r10 release];

[r11 release];

[r6 release];

[r4 release];

r0 = r5;

return r0;

loc_2aaee10:

stack[2050] = r11;

if (r10 != 0x0) {

[r8 pendingLoginRequest:r10];

}

r6 = [[LogAdapter getInstance] retain];

stack[2052] = r8;

r8 = [[r10 getApiName] retain];

r11 = [[r10 getApiVersion] retain];

r5 = [[NSString stringWithFormat:@"[LoginAdapter] apiName: %@, apiVersion: %@ pull login module", r8, r11] retain];

[r6 warn:r5];

[r5 release];

[r11 release];

[r8 release];

[r6 release];

r5 = [stack[2052] retain];

stack[2068] = [stack[2054] retain];

r11 = stack[2050];

stack[2060] = [r5 retain];

r4 = stack[2053];

stack[2061] = [r11 retain];

[stack[2052]->_login_service loginWithLoginOption:stack[2051] extraInfo:r4 completionHandler:sp + 0x38 cancelationHandler:sp + 0x1c, stack[2050], stack[2051], stack[2052], stack[2053], stack[2054], __NSConcreteStackBlock, 0xc2000000, 0x0];

[stack[2061] release];

[stack[2060] release];

[stack[2068] release];

[r5 release];

r5 = 0x2;

goto loc_2aaf046;

loc_2aaefa8:

if (r10 != 0x0) {

r6 = stack[2054];

if ([r8->_loginPendingRequests count] <= 0xff) {

stack[2052] = r8;

[r8 pendingLoginRequest:r10];

r5 = 0x1;

}

else {

stack[2052] = r8;

r5 = 0x0;

}

}

else {

stack[2052] = r8;

r5 = 0x0;

r6 = stack[2054];

}

if (r6 != 0x0) {

r4 = [[NSDictionary dictionary] retain];

stack[2054] = r6;

(*(r6 + 0xc))(r6, 0x0, r4, *(r6 + 0xc), stack[2048]);

[r4 release];

}

else {

stack[2054] = r6;

}

goto loc_2aaf044;

loc_2aaee0a:

r5 = 0x0;

r4 = stack[2053];

goto loc_2aaf060;

}

与 IDA 相比, Hopper 反编译后的伪代码的逻辑与 IDA 反编译得到的伪代码逻辑类似,但多了 r0~r8 等寄存器,阅读性相较而言差一些,但是,仍然可以根据伪代码还原出源代码。

其实我们可以用这个伪代码来检查我们之前还原的源代码,保证还原代码的正确性。

以上,便是 Hopper 的简单使用。 Hopper 除了可以查看汇编代码以外,还可以直接对 Mach-O 文件进行修改,然后重新生成二进制文件(Demo 版没有这个功能)。

感兴趣的同学可以阅读这篇文章学习, 《如何让 Mac 版微信客户端防撤回》http://t.cn/RxzeMIx

推荐阅读更多精彩内容