前言
近期實戰了一次 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 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 會出現About和Support 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 Disassembler, the reverse engineering tool that lets you disassemble, decompile and debug your applications.
Hopper 反匯編工具,是逆向工程的工具,能夠讓你進行反匯編、反編譯并且調試你的應用。
Hopper 的官網是https://www.hopperapp.com/,與 IDA 相同,官網也提供了 Demo 的試用版。試用版也可以進行反匯編,但是不能保存 *.hop 文件。所以,用于學習的話 Hopper 的 Demo 版就足夠了。
與 IDA 不同, Hopper 不能直接將 Mach-O 文件導入工作區,需要下載應用的 *.ipa 文件,并將文件后綴改為 *.zip,解壓該 zip 文件后就可看到 Payload 文件目錄,在該目錄下就存有對應應用的 package 包文件。右擊該 package 出現菜單,選擇Show Package Contents選項,就可看到包內容。
認識 Hopper 工作區
與 IDA 類似, Hopper 也有二進制文件的解析進度條,左邊是符號 Label 等區域,中間是 ARM 的匯編代碼,右邊是相關信息欄。
如圖,在最左列的搜索欄中輸入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