天天看點

MacOS微信逆向分析-Frida

MacOS微信逆向分析-Frida

0.前言

PC下的微信二次開發相信大家都會了,那麼本篇文章将帶領大家使用Frida架構對Mac下微信來進行二次開發!

PS:還有一種靜态注入的方式也不錯,但是考慮到大家xcode安裝包太大就不在這裡展開啦。

PS:frida如何去使用大家得自己去學,本文不過多展開。

主要功能涉及如下:

  1. 微信消息發送
  2. 微信消息監聽

1.微信版本

MacOS微信逆向分析-Frida

2.工具

預先善其事,必先利其器!請先準備如下分析工具

  1. Hopper Disassembler
  2. Class-dump
  3. Frida
  4. Pycharm(可選)
  5. Vscode(可選)

3.Dump 出頭檔案

首先利用Class-Dump拿到微信的頭檔案,打開終端執行:

class-dump -H /Applications/WeChat.app
           

成功執行之後會生成很多的頭檔案了,如下所示

-rw-r--r--  1 n  staff   927B  2 15 19:19 WXCPbQcwxtalkPackage.h
-rw-r--r--  1 n  staff   975B  2 15 19:19 WXCPbReportItem.h
-rw-r--r--  1 n  staff   1.7K  2 15 19:19 WXCPbSCAddVoiceGroupMemberResp.h
-rw-r--r--  1 n  staff   772B  2 15 19:19 WXCPbSCCancelCreateVoiceGroupResp.h
-rw-r--r--  1 n  staff   7.2K  2 15 19:19 WXCPbSCCreateVoiceGroupResp.h
-rw-r--r--  1 n  staff   6.9K  2 15 19:19 WXCPbSCEnterVoiceRoomResp.h
-rw-r--r--  1 n  staff   1.1K  2 15 19:19 WXCPbSCExitVoiceRoomResp.h
-rw-r--r--  1 n  staff   1.2K  2 15 19:19 WXCPbSCModifyVoiceGroupInfoResp.h
-rw-r--r--  1 n  staff   872B  2 15 19:19 WXCPbSCSubscribeLargeVideoResp.h
-rw-r--r--  1 n  staff   867B  2 15 19:19 WXCPbSCSubscribeVideoResp.h
-rw-r--r--  1 n  staff   2.0K  2 15 19:19 WXCPbSCVoiceClientSceneReportResp.h
-rw-r--r--  1 n  staff   864B  2 15 19:19 WXCPbSCVoiceGetGroupInfoBatchResp.h
-rw-r--r--  1 n  staff   637B  2 15 19:19 WXCPbSCVoiceMemberWhisperResp.h
-rw-r--r--  1 n  staff   5.9K  2 15 19:19 WXCPbSCVoiceRedirectResp.h
-rw-r--r--  1 n  staff   1.1K  2 15 19:19 WXCPbSCVoiceRoomHelloResp.h
-rw-r--r--  1 n  staff   904B  2 15 19:19 WXCPbSKBuiltinBuffer_t.h
-rw-r--r--  1 n  staff   686B  2 15 19:19 WXCPbSubscribeVideoMember.h
-rw-r--r--  1 n  staff   2.7K  2 15 19:19 WXCPbSwitchVideoGroupResp.h
-rw-r--r--  1 n  staff   1.4K  2 15 19:19 WXCPbVideoGroupMember.h
-rw-r--r--  1 n  staff   671B  2 15 19:19 WXCPbVoiceClientScene.h
-rw-r--r--  1 n  staff   1.2K  2 15 19:19 WXCPbVoiceClientSceneExt.h
-rw-r--r--  1 n  staff   2.9K  2 15 19:19 WXCPbVoiceConf.h
           

4.分析

首先那麼多的檔案我們肯定不能一個個的去看,那樣效率太低。相信大家做開發為了自己好維護代碼,肯定不會給對象随便命名為abc這種吧!不會吧!不會吧!真的有這種人啊!!!但是我相信騰訊的程式員肯定不會這麼做!!微信核心的功能是啥?是發消息哦,那麼消息的英文是啥?Message !對就是他。是以我們就先塞選下這個Message!

# n @ localhost in ~/vscodewsp/wechat/dump [20:58:22]
$ ll |wc -l
    4922

# n @ localhost in ~/vscodewsp/wechat/dump [20:58:29]
$ ll -l |grep Message|wc -l
     157

# n @ localhost in ~/vscodewsp/wechat/dump [20:58:42]
           

執行如上指令我們把檔案數從4922個轉變到157了。這樣就縮小了範圍啦!如何再次縮小範圍尼!那麼就得是看大家的開發習慣啦,我一般做業務我都喜歡寫service,controller,這種業務類名,于是我再次....

# n @ localhost in ~/vscodewsp/wechat/dump [20:58:42]
$ ll -l |grep Message|grep Service|wc -l
       9

# n @ localhost in ~/vscodewsp/wechat/dump [21:02:13]
$ ll -l |grep Message|grep Service
-rw-r--r--  1 n  staff   5.1K  2 15 19:19 FTSFileMessageService.h
-rw-r--r--  1 n  staff   382B  2 15 19:19 IMessageServiceAppExt-Protocol.h
-rw-r--r--  1 n  staff   980B  2 15 19:19 IMessageServiceFileExt-Protocol.h
-rw-r--r--  1 n  staff   381B  2 15 19:19 IMessageServiceFileReTransferExt-Protocol.h
-rw-r--r--  1 n  staff   755B  2 15 19:19 IMessageServiceImageExt-Protocol.h
-rw-r--r--  1 n  staff   780B  2 15 19:19 IMessageServiceVideoExt-Protocol.h
-rw-r--r--  1 n  staff   407B  2 15 19:19 IMessageServiceVideoReTransferExt-Protocol.h
-rw-r--r--  1 n  staff   3.1K  2 15 19:19 MMFTSMessageService.h
-rw-r--r--  1 n  staff    20K  2 15 19:19 MessageService.h

# n @ localhost in ~/vscodewsp/wechat/dump [21:02:25]
$
           

哎呦哎呦,就剩9個檔案啦???那麼這個一個個看也不礙事!!有時間就是任性!!!哼。最終定位到MessageService.h 打開一看,果然尼!真是運氣好!

- (id)SendLocationMsgFromUser:(id)arg1 toUser:(id)arg2 withLatitude:(double)arg3 longitude:(double)arg4 poiName:(id)arg5 label:(id)arg6;
- (id)SendNamecardMsgFromUser:(id)arg1 toUser:(id)arg2 containingContact:(id)arg3;
- (id)SendStickerStoreEmoticonMsgFromUsr:(id)arg1 toUsrName:(id)arg2 md5:(id)arg3 productID:(id)arg4;
- (id)SendEmoticonMsgFromUsr:(id)arg1 toUsrName:(id)arg2 md5:(id)arg3 emoticonType:(unsigned int)arg4;
- (id)SendImgMessage:(id)arg1 toUsrName:(id)arg2 thumbImgData:(id)arg3 midImgData:(id)arg4 imgData:(id)arg5 imgInfo:(id)arg6;
- (id)SendTextMessage:(id)arg1 toUsrName:(id)arg2 msgText:(id)arg3 atUserList:(id)arg4;
- (id)SendAppMusicMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbnailData:(id)arg6;
- (id)SendAppURLMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbnailData:(id)arg6;
- (id)SendAppURLMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbUrl:(id)arg6 sourceUserName:(id)arg7 sourceDisplayName:(id)arg8;

           

你看這功能不就來了嘛?Send開頭的都是發送消息的函數啊。OK完事。那麼就開始搞它!

PS:其實分析時候還是挺費事的,但是大家自己多動手肯定能找到的!

5.FridaHook驗證

為了驗證自己的分析是不是正确的,我們得進行驗證啊,怎麼驗證?frida大法好!執行以下指令:

frida-trace -m "-[MessageService Send*]" 微信

$ frida-trace -m "-[MessageService Send*]" 微信
Instrumenting...                                                        
-[MessageService SendTextMessageWithString:toUser:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendTextMessageWithString_toUser_.js"
-[MessageService SendAppURLMessageFromUser:toUsrName:withTitle:url:description:thumbUrl:sourceUserName:sourceDisplayName:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendAppURLMessageFromUser_toUsrN_eaefd0af.js"
------------------------------------------------------------------------------
-[MessageService SendNamecardMsgFromUser:toUser:containingContact:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendNamecardMsgFromUser_toUser_c_b5899e8d.js"
Started tracing 18 functions. Press Ctrl+C to stop.  
           

然後會在目前目錄生成__handlers__檔案夾,裡面是frida為我們自動生成的hook腳本檔案。我們使用微信發送一條消息試試。

然後終端會輸出一條資訊:

195323 ms -[MessageService SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x6000002ec860 atUserList:0x600000a73570]

這個就是觸發了發送消息的hook資訊啦。SendTextMessage 是不是跟我們在頭檔案資訊裡面看到的一樣。

我們找到handles檔案夾下SendTextMessage這個js檔案,試試修改log輸出然後再執行

frida-trace -m "-[MessageService Send*]" 微信

我們可以看到輸出變啦

2908 ms -[我的消息測試 SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x6722df8306c2767b atUserList:0x6000009c2760]

如此可以确定我們找到的函數就是發送消息的函數。那麼看看能不能列印出自己發送的消息内容

- (id)SendTextMessage:(id)arg1 toUsrName:(id)arg2 msgText:(id)arg3 atUserList:(id)arg4;

可以看到這個函數一共有4個參數:參數一:暫時不知道。參數二:toUsrName,我們可以知道是消息發送給誰的。參數三:msgText 消息内容,消息四:暫時不知道

分别把這四個參數給列印出來試試!修改js檔案

onEnter(log, args, state) {
    console.log(`-[我的消息測試 SendTextMessage:${args[2]} toUsrName:${args[3]} msgText:${args[4]} atUserList:${args[5]}]`);
    console.log("arg[1] -> " + new ObjC.Object(args[2]))
    console.log("arg[2] -> " + new ObjC.Object(args[3]))
    console.log("arg[3] -> " + new ObjC.Object(args[4]))
    console.log("arg[4] -> " + new ObjC.Object(args[5]))
  },
           

然後執行

frida-trace -m "-[MessageService Send*]" 微信

發送一條消息

MacOS微信逆向分析-Frida
arg[1] -> wxid_*****63i822
arg[2] -> filehelper
arg[3] -> 這個是消息測試
arg[4] -> 
           /* TID 0x307 */
 14534 ms  -[我的消息測試 SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x600000adefd0 atUserList:0x600000add470]
           

我們可以看到終端正确響應了,輸出的正是我們發送的消息。那麼我修改發送内容試試??添加如下代碼:

args[4] = ObjC.classes.NSString.stringWithString_("MacOS微信分析")

然後微信發送任何消息,對方都将收到的是MacOS微信分析

MacOS微信逆向分析-Frida

這樣我們就确定了發送文本消息的函數就是這個。那麼我們如何主動調用它呢?

6.Hopper分析程式代碼

從上面的分析我們看到發送消息需要四個參數。第一個:通過分析應該是我們自己的微信id,第二個:對方的微信id,第三個:消息内容,第四個:可以為null

那麼就打開hopper拖入微信具體分析分析吧

應用程式->微信->顯示包内容->Contents->MacOS->WeChat 拖進hopper然後預設選項即可

MacOS微信逆向分析-Frida

在左邊輸入SendTextMessage搜尋我們可以看到上面四個應該是我們所需要的,都打開看下僞代碼。(我們分析需要找到函數調用的地方就能知道傳參,然後再去分析參數是如何而來。那麼除了函數定義地方代碼,其餘的都可以找到。

MMMessageSendLogic :

/* @class MMMessageSendLogic */
-(unsigned char)sendTextMessageWithString:(void *)arg2 mentionedUsers:(void *)arg3 {
    r14 = self;
    r15 = [arg2 retain];
    r12 = [arg3 retain];
    r13 = [[CUtility filterStringForTextMessage:r15] retain];
    [r15 release];
    if ([r13 length] != 0x0) {
            stack[-64] = r12;
            rax = [r13 lengthOfBytesUsingEncoding:0x4];
            rbx = rax;
            if (rax >= 0x4001) {
                    rax = [[NSString alloc] initWithFormat:@"ERROR: Text too long, length: %lu, utf8 length: %lu", [r13 length], rbx];
                    stack[0] = "-[MMMessageSendLogic sendTextMessageWithString:mentionedUsers:]";
                    [MMLogger logWithMMLogLevel:0x2 module:"ComposeInputView" file:0x103e0e162 line:0x112 func:stack[0] message:rax];
                    [rax release];
                    rax = [NSBundle mainBundle];
                    rax = [rax retain];
                    stack[-72] = rax;
                    r15 = [[rax localizedStringForKey:@"Message.Input.Too.Long.Title" value:@"" table:0x0] retain];
                    rax = [NSBundle mainBundle];
                    rax = [rax retain];
                    r14 = rax;
                    rax = [rax localizedStringForKey:@"Message.Input.Too.Long.Content" value:@"" table:0x0];
                    rax = [rax retain];
                    [NSAlert showAlertSheetWithTitle:r15 message:rax completion:0x0];
                    [rax release];
                    [r14 release];
                    [r15 release];
                    [stack[-72] release];
                    r14 = 0x0;
                    r12 = stack[-64];
            }
            else {
                    rax = [WeChat sharedInstance];
                    rax = [rax retain];
                    r15 = [[rax CurrentUserName] retain];
                    [rax release];
                    rax = [r14 currnetChatContact];
                    rax = [rax retain];
                    r14 = [[rax m_nsUsrName] retain];
                    [rax release];
                    r12 = [[MMServiceCenter defaultCenter] retain];
                    objc_unsafeClaimAutoreleasedReturnValue([[[r12 getService:[MessageService class]] retain] SendTextMessage:r15 toUsrName:r14 msgText:r13 atUserList:stack[-64]]);
                    [rax release];
                    [r12 release];
                    [r14 release];
                    [r15 release];
                    r14 = 0x1;
                    r12 = stack[-64];
                    r13 = r13;
            }
    }
    else {
            rax = [[NSString alloc] initWithFormat:@"ERROR: Text is empty, can't send"];
            stack[0] = "-[MMMessageSendLogic sendTextMessageWithString:mentionedUsers:]";
            [MMLogger logWithMMLogLevel:0x2 module:"ComposeInputView" file:0x103e0e162 line:0x10c func:stack[0] message:rax];
            [rax release];
            r14 = 0x0;
    }
    [r13 release];
    [r12 release];
    rax = r14 & 0xff;
    return rax;
}

           

這個僞代碼看的就比較清楚了,

objc_unsafeClaimAutoreleasedReturnValue([[[r12 getService:[MessageService class]] retain] SendTextMessage:r15 toUsrName:r14 msgText:r13 atUserList:stack[-64]]);

我們可以看到第一個參數是r15,網上追溯r15,

r15 = [[rax CurrentUserName] retain];

r15是這裡指派的,那麼再看看CurrentUserName方法内容。

-(void *)CurrentUserName {
    if ([self isLoggedIn] != 0x0) {
            rdi = [[CUtility GetCurrentUserName] retain];
    }
    else {
            rdi = 0x0;
    }
    rax = [rdi autorelease];
    return rax;
}
           

可以看到是先判斷是不是已經登入,然後調用CUtility類裡面的GetCurrentUserName方法獲得的。那麼第一個參數我們就知道了。其餘三個參數我們也很容易的可以手動構造。我們編寫js腳本代碼

7.編寫frida腳本

console.log("init success");
function SendTextMessage(wxid, msg) {
    var message = ObjC.chooseSync(ObjC.classes.MessageService)[0]
    var username = ObjC.classes.CUtility.GetCurrentUserName();
    console.log(username)
    console.log("Type of arg[0] -> " + message)
    var toUsrName = ObjC.classes.NSString.stringWithString_(wxid);
    var msgText = ObjC.classes.NSString.stringWithString_(msg);
    message["- SendTextMessage:toUsrName:msgText:atUserList:"](username, toUsrName, msgText, null);
}
SendTextMessage("filehelper","主動調用發送資訊!")
           

将以上文本儲存js檔案,然後執行以下指令:

frida 微信 --debug --runtime=v8 --no-pause -l test.js

我們就可以看到微信上發送了一條消息

MacOS微信逆向分析-Frida

8.消息監聽

未完待續。