iOS逆向-微信自動新增好友

tkkk發表於2018-01-29

前言

上次完成了 macOS 版微信小助手,現在終於有(xian)時(de)間(huang)來說說 iOS 逆向了。本篇主要實現在微信上自動新增好友(即自動驗證新的朋友申請),從而熟悉 iOS 逆向分析的過程,可能總結的有點粗糙,如果有不懂的地方歡迎探討。

github地址: iOS 版微信小助手(防撤回、修改微信運動、群管理、好友請求管理)

工具

以下工具的詳細使用方法可以檢視iOS應用逆向工程 第2版 第二部分 工具篇。

macbook 軟體

  • theos

    製作 Tweak 的工具

  • hopper disassembler

    用於靜態分析

  • usbmuxd

    埠轉發,可以讓我們通過 usb 連線手機進行 ssh、lldb 除錯等。主要使用python-client目錄下的檔案

  • class-dump

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地址
複製程式碼

下載 ldident,進行

ldid - Sent.xml debugserver
複製程式碼

在使用 ssh 拷貝至手機,完成。

分析

思路:想要實現自動驗證好友請求,則要拿到獲取好友請求的方法,以及通過好友請求的方法。hook 獲取好友請求的方法,在接收到好友請求的時候,執行新增好友的方法。 而這些主要邏輯在“新的朋友”介面。

定位好友請求的方法

UI 分析

我們知道,根據 MVC 模式,一般的方法實現都是在 ViewController 中的,所以想要拿到好友請求的方法,要先拿到當前介面的控制器。而這時候可以通過 UI 分析獲得。

先開啟新的朋友介面。

iOS逆向-微信自動新增好友

使用 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

啟動Cycript後,使用以下命令檢視當前 UI 佈局

UIApp.keyWindow.recursiveDescription().toString()
複製程式碼

螢幕快照 2017-04-01 上午10.57.28.png

因為知道當前的檢視有 tableView,所以找到 tableView 的物件。從上圖可以看到該物件的地址為0x18c4be00。 在使用 nextResponder()根據響應者往上找當前的 ViewController。

螢幕快照 2017-04-01 上午11.01.03.png
找到當前的控制器,為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

螢幕快照 2017-04-01 上午11.22.48.png

說明有好友新增請求的時候,會呼叫 -[SayHelloViewController OnSayHelloDataChange]

動態分析

既然已經知道了有好友請求的時候會呼叫OnSayHelloDataChange,那麼我們可以在當前方法中進行處理,然而有個弊端,就是當有好友請求時,微信不在新的朋友介面時,是不會呼叫該方法的。所以我們應該在更底層的類中(假設為訊息管理類)中進行處理,而怎麼找到訊息管理類呢?按照一般的邏輯,訊息管理類中一定有方法觸發了OnSayHelloDataChange,這時候就要用到 lldb + hopper 神器來找到相應的訊息管理類與其處理方法了。

lldb 進行手機端除錯,hopper 進行靜態分析,分析OnSayHelloDataChange方法的資訊,找出突破口。

先用 hopper 開啟微信的二進位制檔案。搜尋SayHelloViewController OnSayHelloDataChange方法。 可以看到當前方法在微信中的偏移地址0x14a4824。

QQ20170401-113150@2x.png

啟動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(這個值不是定值)

QQ20170401-113326@2x.png

通過以上可以找到 [SayHelloViewController OnSayHelloDataChange]方法在手機上的記憶體地址。即

記憶體地址 = 程式記憶體基地址 + 方法偏移地址
複製程式碼

使用br打斷點檢視

 br s -a "0x000b2000 + 0x14a4824"
複製程式碼

螢幕快照 2017-04-01 上午11.39.22.png

接著輸入c繼續執行,重新使用另一微信賬號新增好友,會觸發該斷點。

螢幕快照 2017-04-01 上午11.41.35.png

使用bt檢視呼叫棧資訊,即哪些方法呼叫了當前的方法,找到方法的上游。(非同步呼叫的話沒辦法檢視)

QQ20170401-114245@2x.png

第一個表示當前的方法,可以看到在呼叫此方法前,該程式總共呼叫了3個方法。 分別計算出這三個方法在微信中的偏移量。

螢幕快照 2017-04-01 上午11.48.13.png

將這三個地址在 hopper 中檢視(按快捷鍵g,輸入地址),找到了對應的方法為

// 呼叫的順序為從下到上
[SayHelloViewController OnSayHelloDataChange]
[SayHelloDataLogic onFriendAssistAddMsg:]
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
[CMessageMgr MainThreadNotifyToExt:]
複製程式碼

從以上方法名可以猜測

[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
複製程式碼

是用來接收新增好友訊息的函式處理,其中MsgList:後面的引數可能為訊息的陣列,為了證明我們可以在該方法中打個斷點檢視下。 使用命令register read讀取暫存器地址,並使用po列印該物件。

螢幕快照 2017-04-01 下午2.05.24.png

看出r3暫存器確實是個陣列,同時也得到了訊息的物件為CMessageWrap 證明我們是對的。

ps: 解釋下為什麼要看r3,因為在 armv7 中,一個方法的呼叫,一般暫存器都是這麼儲存的:前四個引數放在r0~r3,剩下的存放在堆疊中。檢視堆疊的話使用x/10 $sp 檢視前10個堆疊裡的物件地址。

然而FriendAsistSessionMgr這個類可能在新的好友介面進行一些初始化,且放在SayHelloViewController中,而我們想要的是不管在哪個控制器裡都可以 hook 住上面的訊息陣列物件。因此我們往上找,[CMessageMgr MainThreadNotifyToExt:],然而裡面並沒有我們需要的資訊。而根據類名我們推測CMessageMgr是用來管理訊息的。有可能是在非同步執行了訊息陣列的獲取。

因此,重複以上步驟,使用 logify 對CMessageMgr進行 Log 分析。最終鎖定了 CMessageMgr MessageReturn:MessageInfo:Event:

螢幕快照 2017-04-05 下午3.02.50.png

定位通過好友請求的方法

動態分析

既然找到了接收好友請求的方法,那麼是時候找通過好友請求的方法了。 我們知道,通過好友請求的方法,是在新的朋友介面,點選接受的時候觸發的。(可以通過 Log 分析,然而這裡還有另一個比較快速的方法)

iOS逆向-微信自動新增好友
Cycript 定位

先通過 Cycript 列印出所有的 UI 層級。 找到接受按鈕的物件,(有個技巧,我們知道當前按鈕是在某個 cell 下面的,所以定位這個)。

WX20170405-152315@2x.png
再通過cycript將該物件的 hidden 動態修改為 1,看是否隱藏。

#0x186922f0.hidden = 1
複製程式碼

發現按鈕不見了,證明我們是對的。這時候需要找到點選按鈕的事件。

而我們知道 UIButton 是繼承 UIControl 的,在 Cycript 中, 可以通過allTargetsallControlEvents檢視當前UIControl所有的targets與events,再使用actionsForTarget:forControlEvent:從而找到觸發的方法。

螢幕快照 2017-04-05 下午3.30.08.png

看出所觸發的方法為[ContactsItemView onRightBtnAction]

靜態分析

既然拿到了方法名,那我們怎麼看他具體的實現呢? 接下來就是大名鼎鼎的 hopper 登場了。 用 hopper 開啟微信的二進位制檔案,並進行彙編與虛擬碼的轉換。 ~~由於彙編讀起來比較晦澀,所以還是進行虛擬碼的轉換,這樣效率比較快。~~點選該按鈕進行轉換

QQ20170401-094655@2x.png

可以得到虛擬碼

螢幕快照 2017-04-05 下午3.56.21.png
上圖我們看到了

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:]. 而 ContactsItemViewdelegateSayHelloViewController

再用 hopper 定位onContactsItemViewRightButtonClick

螢幕快照 2017-04-05 下午4.05.49.png
螢幕快照 2017-04-05 下午4.14.09.png

看到這裡估計會很懵逼不知道從何下手。這時候只要加以推測就可以了。 上圖中進行了兩個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:方法。主要的部分如下所示。

Paste_Image.png

通過分析,我們可以得到,確認好友申請,顯示構造了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 分析。

參考

iOS應用逆向工程 第2版
移動App入侵與逆向破解技術-iOS篇

相關文章