前言
上次完成了 macOS 版微信小助手,現在終於有(xian)時(de)間(huang)來說說 iOS 逆向了。本篇主要實現在微信上自動新增好友(即自動驗證新的朋友申請),從而熟悉 iOS 逆向分析的過程,可能總結的有點粗糙,如果有不懂的地方歡迎探討。
github地址: iOS 版微信小助手(防撤回、修改微信運動、群管理、好友請求管理)
工具
以下工具的詳細使用方法可以檢視iOS應用逆向工程 第2版 第二部分 工具篇。
macbook 軟體
-
製作 Tweak 的工具
-
用於靜態分析
-
埠轉發,可以讓我們通過 usb 連線手機進行 ssh、lldb 除錯等。
主要使用python-client
目錄下的檔案
dump 目標物件的 class 資訊的工具.
- lldb
除錯神器,用過的都說好。預設自帶,在
/Applications/Xcode.app/Contents/Developer/usr/bin/lldb
中。
越獄 iPhone 軟體
以下軟體在 Cydia 中即可下載(debugserver 除外)
- OpenSSH
實現在越獄手機上遠端進行 ssh 服務,通過 ssh,即可以通過終端連線 iPhone 進行控制。
**iOS 工具大部分都需要在 ssh 環境中使用**
複製程式碼
- Cycript
指令碼語言,用於 hook 正在執行的程式,並實時注入程式碼。
- ondeviceconsole
用於在 Terminal 中檢視手機的 log
- debugserver
用於連線手機進行 lldb 除錯的工具。用 Xcode 在手機上進行 app 除錯即可在iPhone目錄的
/Developer/usr/bin/
中生成。
使用 debugserver 需要先進行處理。因為缺少task_for_pid
許可權,所以除錯不了其他的 app。
先通過 ssh 拷貝 debugserver
scp root@iOSIP:/Developer/usr/bin/debugserver ~/debugserver // iOSIP 為手機的ip地址
複製程式碼
ldid - Sent.xml debugserver
複製程式碼
在使用 ssh 拷貝至手機,完成。
分析
思路:想要實現自動驗證好友請求,則要拿到獲取好友請求的方法,以及通過好友請求的方法。hook 獲取好友請求的方法,在接收到好友請求的時候,執行新增好友的方法。 而這些主要邏輯在“新的朋友”介面。
定位好友請求的方法
UI 分析
我們知道,根據 MVC 模式,一般的方法實現都是在 ViewController 中的,所以想要拿到好友請求的方法,要先拿到當前介面的控制器。而這時候可以通過 UI 分析獲得。
先開啟新的朋友介面。
使用 usbmuxd
進行埠的轉發(若手機不卡,可以跳過這步直接使用 ssh 進行 wifi 遠端連線)
python tcprelay.py -t 22:2222
複製程式碼
再使用 ssh 連線至手機
ssh root@localhost -p 2222
// ssh root@192.168.31.94 如果是wifi連線,請檢視當前手機的wifi地址。
複製程式碼
檢視微信的程式資訊
ps -e |grep WeChat
複製程式碼
Cycript 注入
cycript -p WeChat // 或者是當前微信的程式號,如下所示
複製程式碼
啟動Cycript
後,使用以下命令檢視當前 UI 佈局
UIApp.keyWindow.recursiveDescription().toString()
複製程式碼
因為知道當前的檢視有 tableView,所以找到 tableView 的物件。從上圖可以看到該物件的地址為0x18c4be00。
在使用 nextResponder()
根據響應者往上找當前的 ViewController。
SayHelloViewController
Log 分析
使用class-dump
dump 出微信的 class 資訊。
class-dump -S -s -H demo.app -o ~/Document/headers/
// dump 微信app的標頭檔案儲存在 ~/Document/headers/ 目錄中
複製程式碼
再使用 theos 的 logify 工具,該工具用來注入NSLog
來列印方法的入參和出參。(就是在 hook 某個類的所有的方法,並在裡面加 log,並匯出xm檔案)
logify.pl ~/Document/headers/SayHelloViewController.h > ~/Desktop/Tweak.xm
複製程式碼
**注意:**一般該Tweak.xm仍然無法執行,需要進行修改:
- 去掉 .cxx_destruct 方法
- 將 HBLogDebug 改為 NSLog
- 去掉所有的 delegate
- 將所有的引數物件型別改成 id
- 去掉所有的 weak
再使用 theos 配置相關檔案(具體檢視iOS逆向-微信helloWorld), 然後進行make package install 安裝至手機。
重新啟動微信進入新的朋友介面。
在ssh中使用ondeviceconsole
列印手機的 log。
這時用另一個微訊號新增自己好友。觸發好友請求的方法。可以看到以下的 log
說明有好友新增請求的時候,會呼叫
-[SayHelloViewController OnSayHelloDataChange]
動態分析
既然已經知道了有好友請求的時候會呼叫OnSayHelloDataChange
,那麼我們可以在當前方法中進行處理,然而有個弊端,就是當有好友請求時,微信不在新的朋友介面時,是不會呼叫該方法的。所以我們應該在更底層的類中(假設為訊息管理類)中進行處理,而怎麼找到訊息管理類呢?按照一般的邏輯,訊息管理類中一定有方法觸發了OnSayHelloDataChange
,這時候就要用到 lldb + hopper 神器來找到相應的訊息管理類與其處理方法了。
lldb 進行手機端除錯,hopper 進行靜態分析,分析OnSayHelloDataChange
方法的資訊,找出突破口。
先用 hopper 開啟微信的二進位制檔案。搜尋SayHelloViewController OnSayHelloDataChange
方法。
可以看到當前方法在微信中的偏移地址0x14a4824。
啟動debugserver 配合lldb除錯
先開啟微信,並使用 usbmuxd 轉換埠
python tcprelay.py -t 1234:1234
複製程式碼
再 ssh 到手機上,開啟 debugserver 。
debugserver *:1234 -a "WeChat"
複製程式碼
使用新的 Terminal 視窗,開啟 lldb,連線1234埠,並檢視當前微信的程式資訊(一般會在所有程式的首行)。
此時會卡住一段時間。
// 開啟lldb
/Applications/Xcode.app/Contents/Developer/usr/bin/lldb
// 連線埠除錯
(lldb) process connect connect://localhost:1234
// 列印所有程式
(lldb) image list -o -f
複製程式碼
找到微信在當前手機上的程式記憶體基地址為0x000b2000(這個值不是定值)
通過以上可以找到 [SayHelloViewController OnSayHelloDataChange]
方法在手機上的記憶體地址。即
記憶體地址 = 程式記憶體基地址 + 方法偏移地址
複製程式碼
使用br
打斷點檢視
br s -a "0x000b2000 + 0x14a4824"
複製程式碼
接著輸入c
繼續執行,重新使用另一微信賬號新增好友,會觸發該斷點。
使用bt
檢視呼叫棧資訊,即哪些方法呼叫了當前的方法,找到方法的上游。(非同步呼叫的話沒辦法檢視)
第一個表示當前的方法,可以看到在呼叫此方法前,該程式總共呼叫了3個方法。 分別計算出這三個方法在微信中的偏移量。
將這三個地址在 hopper 中檢視(按快捷鍵g,輸入地址),找到了對應的方法為
// 呼叫的順序為從下到上
[SayHelloViewController OnSayHelloDataChange]
[SayHelloDataLogic onFriendAssistAddMsg:]
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
[CMessageMgr MainThreadNotifyToExt:]
複製程式碼
從以上方法名可以猜測
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
複製程式碼
是用來接收新增好友訊息的函式處理,其中MsgList:
後面的引數可能為訊息的陣列,為了證明我們可以在該方法中打個斷點檢視下。
使用命令register read
讀取暫存器地址,並使用po
列印該物件。
看出r3暫存器確實是個陣列,同時也得到了訊息的物件為CMessageWrap
證明我們是對的。
ps: 解釋下為什麼要看r3,因為在 armv7 中,一個方法的呼叫,一般暫存器都是這麼儲存的:前四個引數放在r0~r3,剩下的存放在堆疊中。檢視堆疊的話使用x/10 $sp
檢視前10個堆疊裡的物件地址。
然而FriendAsistSessionMgr
這個類可能在新的好友介面進行一些初始化,且放在SayHelloViewController
中,而我們想要的是不管在哪個控制器裡都可以 hook 住上面的訊息陣列物件。因此我們往上找,[CMessageMgr MainThreadNotifyToExt:]
,然而裡面並沒有我們需要的資訊。而根據類名我們推測CMessageMgr
是用來管理訊息的。有可能是在非同步執行了訊息陣列的獲取。
因此,重複以上步驟,使用 logify 對CMessageMgr
進行 Log 分析。最終鎖定了
CMessageMgr MessageReturn:MessageInfo:Event:
定位通過好友請求的方法
動態分析
既然找到了接收好友請求的方法,那麼是時候找通過好友請求的方法了。
我們知道,通過好友請求的方法,是在新的朋友介面,點選接受的時候觸發的。(可以通過 Log 分析,然而這裡還有另一個比較快速的方法)
Cycript 定位
先通過 Cycript 列印出所有的 UI 層級。 找到接受按鈕的物件,(有個技巧,我們知道當前按鈕是在某個 cell 下面的,所以定位這個)。
再通過cycript將該物件的 hidden 動態修改為 1,看是否隱藏。#0x186922f0.hidden = 1
複製程式碼
發現按鈕不見了,證明我們是對的。這時候需要找到點選按鈕的事件。
而我們知道 UIButton 是繼承 UIControl 的,在 Cycript 中, 可以通過allTargets
與 allControlEvents
檢視當前UIControl所有的targets與events,再使用actionsForTarget:forControlEvent:
從而找到觸發的方法。
看出所觸發的方法為[ContactsItemView onRightBtnAction]
靜態分析
既然拿到了方法名,那我們怎麼看他具體的實現呢? 接下來就是大名鼎鼎的 hopper 登場了。 用 hopper 開啟微信的二進位制檔案,並進行彙編與虛擬碼的轉換。 ~~由於彙編讀起來比較晦澀,所以還是進行虛擬碼的轉換,這樣效率比較快。~~點選該按鈕進行轉換
可以得到虛擬碼
上圖我們看到了r10 = self;
r5 = r10 + *0x33befe8;
r4 = objc_loadWeakRetained(r5);
r8 = @selector(onContactsItemViewRightButtonClick:);
r11 = [r4 respondsToSelector:r8];
複製程式碼
可以得出,r11 = [r5 onContactsItemViewRightButtonClick:btn]
,而 r5 我們判斷為 self 的代理,這個我們也可以通過在之前用 class-dump 的標頭檔案裡面搜尋onContactsItemViewRightButtonClick
,會發現在ContactsItemViewDelegate
中。
也就是[ContactsItemView onRightBtnAction]
內部呼叫了[self.delegate onContactsItemViewRightButtonClick:]
.
而 ContactsItemView
的delegate
為 SayHelloViewController
。
再用 hopper 定位onContactsItemViewRightButtonClick
。
看到這裡估計會很懵逼不知道從何下手。這時候只要加以推測就可以了。 上圖中進行了兩個if判斷,第一個為
r10 = @selector(class);
r2 = loc_1c099bc(@class(CPushContact), r10);
r1 = @selector(isKindOfClass:);
r5 = loc_1c099bc(r4, r1, r2);
loc_1c099d4(r4);
if ((r5 & 0xff) != 0x0) {
複製程式碼
可以得出其實是執行了 if([r4 isKindOfClass:[CPushContact class]])
;
而r4是什麼呢?可以肯定是CPushContact
物件,不然下面的程式碼都不執行了。我們可以根據動態分析,通過 lldb 打斷點,並檢視r3暫存器的物件型別,可以看到該物件為CPushContact
物件。因此r4就是CPushContact
物件,根據字面意思可以得到就是聯絡人物件。
繼續看下面的程式碼,可以看到也進行了一次判斷if (((loc_1c099bc(r6, @selector(m_bSuspiciousUser)) & 0xff) != 0x0) && ((loc_1c099bc(r6, @selector(isMMContact)) & 0xff) == 0x0))
,看到了MMUIAlertView
。推測是彈窗的 view ,推測如果是可疑的使用者或者當前申請的好友已經是自己的好友,那就進行彈窗。而另一部分為verifyContactWithOpCode:opcode:
,推測該部分為新增好友的方法。
可以通過 Log 分析或者通過 lldb 打斷點,會看到都會進入該方法。且引數分別為CPushContact
物件與 3。
接著繼續分析verifyContactWithOpCode:opcode:
方法。主要的部分如下所示。
通過分析,我們可以得到,確認好友申請,顯示構造了CContactVerifyLogic
物件。再構造了一個CVerifyContactWrap
物件,並設定了相關屬性,比如m_nsUsrName
m_uiScene
m_nsTicket
.然後通過新增到陣列中,通過CContactVerifyLogic
物件的startWithVerifyContactWrap:opCode:parentView:fromChatRoom:
方法傳送。
程式碼如下:
CContactVerifyLogic *verifyLogic = [[CContactVerifyLogic alloc] init];
CVerifyContactWrap *wrap = [[CVerifyContactWrap alloc] init];
[wrap setM_nsUsrName:contact.m_nsEncodeUserName];
[wrap setM_uiScene:contact.m_uiFriendScene];
[wrap setM_nsTicket:contact.m_nsTicket];
[wrap setM_nsChatRoomUserName:contact.m_nsChatRoomUserName];
wrap.m_oVerifyContact = contact;
AutoSetRemarkMgr *mgr = [[MMServiceCenter defaultCenter] getService:[AutoSetRemarkMgr class]];
id attr = [mgr GetStrangerAttribute:contact AttributeName:1001];
if([attr boolValue]) {
[wrap setM_uiWCFlag:(wrap.m_uiWCFlag | 1)];
}
[verifyLogic startWithVerifyContactWrap:[NSArray arrayWithObject:wrap] opCode:3 parentView:[UIView new] fromChatRoom:NO];
複製程式碼
這樣我們就得到了 獲取好友請求的方法與新增好友的方法。
而這裡還有一個問題,就是新增好友的物件是CPushContact
,而獲得好友請求的物件的CMessageWrap
。這裡需要進行轉換,而轉換的方法也在SayHelloViewController
中,可以重複上面的分析方法獲得。
編寫Tweak
通過以上的分析,將程式碼合併起來
%hook CMessageMgr
- (void)MessageReturn:(unsigned int)arg1 MessageInfo:(NSDictionary *)info Event:(unsigned int)arg3 {
%orig;
if (arg1 == 332) { // 收到新增好友訊息
NSString *keyStr = [info objectForKey:@"5"];
if ([keyStr isEqualToString:@"fmessage"]) {
NSArray *wrapArray = [info objectForKey:@"27"];
[self addAutoVerifyWithArray:wrapArray];
}
}
}
%new
- (void)addAutoVerifyWithArray:(NSArray *)ary {
[ary enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CPushContact *contact = [%c(SayHelloDataLogic) getContactFrom:obj];
if (![contact isMyContact] && [contact.m_nsDes isEqualToString:autoVerifyKeyword]) {
CContactVerifyLogic *verifyLogic = [[%c(CContactVerifyLogic) alloc] init];
CVerifyContactWrap *wrap = [[%c(CVerifyContactWrap) alloc] init];
[wrap setM_nsUsrName:contact.m_nsEncodeUserName];
[wrap setM_uiScene:contact.m_uiFriendScene];
[wrap setM_nsTicket:contact.m_nsTicket];
[wrap setM_nsChatRoomUserName:contact.m_nsChatRoomUserName];
wrap.m_oVerifyContact = contact;
AutoSetRemarkMgr *mgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(AutoSetRemarkMgr)];
id attr = [mgr GetStrangerAttribute:contact AttributeName:1001];
if([attr boolValue]) {
[wrap setM_uiWCFlag:(wrap.m_uiWCFlag | 1)];
}
[verifyLogic startWithVerifyContactWrap:[NSArray arrayWithObject:wrap] opCode:3 parentView:[UIView new] fromChatRoom:NO];
}
}];
}
複製程式碼
總結
本文為本人根據iOS應用逆向工程 第2版的內容進行分析,由於整個逆向流程有點繁瑣,有時候也不是隻要分析一次就可以成功的,需要反反覆覆的進行UI分析、Log分析、lldb 分析。