
今天開始介紹如何一步步逆向微信app,寫一個自動搶紅包插件。我們最終成品是寫一個能自動搶紅包并且在非越獄手機(jī)上安裝的多開微信。
我暫時(shí)打算分3篇文章介紹整個逆向開發(fā)過程。逆向的是目前為止微信最新版本6.6.1。我們能做到的是微信APP在前臺任何頁面有紅包消息到來時(shí),實(shí)現(xiàn)自動搶紅包。
前提
1. 一臺越獄iPhone手機(jī),最好是iPhone5s及以上型號手機(jī),因?yàn)閺膇Phone5s后全是arm64處理器。
2. 逆向工具集
檢測工具
如:Reveal、tcpdump等反編譯工具(反匯編工具 - 分析二進(jìn)制文件并得到一些信息)
如:IDA、Hopper Disassembler、classdump等調(diào)試工具
如:lldb、Cycript等開發(fā)工具
如:Xcode、theos等-
打包工具
如:iTools、iOS App Signer等
-
輔助工具
如:OPENSSH、vim、ps、network-cmds等
3. 基本的逆向知識和工具使用技能
4. 一顆戒驕戒躁、勇于嘗試的心
關(guān)于逆向開發(fā)和工具集的介紹、使用我在原來的文章中已經(jīng)介紹過,我們在這里不再介紹直接使用。有疑惑或者興趣的童鞋可以關(guān)注我微信公眾號:樂Coding查看。
砸殼導(dǎo)出頭文件
砸殼把微信的Mach-O文件拷貝到電腦,然后用classdump導(dǎo)出頭文件留作備用。這兩步我在逆向第一課中已經(jīng)介紹過不在贅述。如果覺得自己砸殼還得拷貝到電腦麻煩,可以從PP助手上下載相同版本的越獄包。
查找收紅包方法
我們要實(shí)現(xiàn)在任何頁面都能自動搶紅包,首先我們要找到收紅包的接口,然后再實(shí)現(xiàn)拆紅包和搶紅包。
如何找到收紅包的接口呢,我們要從聊天頁面開始。看看當(dāng)紅包消息到來時(shí),哪些方法會被調(diào)用。
一、定位聊天控制器
在越獄手機(jī)上打開微信,點(diǎn)擊進(jìn)入單聊或者群聊頁面。
1. 打開Mac終端,ssh連接到手機(jī)
這里的192.168.220.195是我越獄手機(jī)的IP地址,你操作時(shí)要換成自己手機(jī)的IP地址
ssh root@192.168.220.195
2. 查看微信進(jìn)程ID
ps -e | grep WeChat

3. 使用cycript附加進(jìn)程
cycript -p WeChat
4. 找到當(dāng)前的控制器
找到當(dāng)前的聊天控制器名字可以使用Reveal一眼看到,也可以使用下面我介紹的第二種方法。
4.1 打印UIView對象
UIApp.keyWindow.recursiveDescription().toString()
打印結(jié)果如下:

4.2 選擇任意一個控件,用控件地址調(diào)用nextResponder方法,找到控制器。
如果一步不是,就遞歸調(diào)用,直到nextResponder是ViewController為止。
我們以上圖中的藍(lán)色標(biāo)注ImageView為例查找。

所以聊天的控制器就是BaseMsgContentViewController。查看我們導(dǎo)出的頭文件也能找到同名的BaseMsgContentViewController.h文件。
二、 創(chuàng)建Tweak項(xiàng)目
我們已經(jīng)找到了聊天頁面的控制器名,并且發(fā)現(xiàn)有同名的頭文件。下面我們先來創(chuàng)建一個Tweak項(xiàng)目用于以后的調(diào)試。
在Mac上創(chuàng)建微信的Tweak項(xiàng)目,不清楚的請移步逆向第二課。
創(chuàng)建命令和最終項(xiàng)目目錄如下圖:


創(chuàng)建成功后,Makefile初始內(nèi)容:
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = WXRedTweak
WXRedTweak_FILES = Tweak.xm
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 WeChat"
添加一些便于測試的配置,修改后如下:
ARCHS = armv7 arm64 #支持cpu類型
TARGET = iphone:latest:8.0 #最低支持版本
THEOS_DEVICE_IP = 192.168.220.195 #手機(jī)的ip
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = WXRedTweak
WXRedTweak_FILES = Tweak.xm
WXRedTweak_FRAMEWORKS = UIKit #需要的框架
include $(THEOS_MAKE_PATH)/tweak.mk
clean::
rm -rf ./packages/*
after-install::
install.exec "killall -9 WeChat"
三、查找BaseMsgContentViewController中接收消息的方法
1. 使用logify.pl給BaseMsgContentViewController所有函數(shù)添加log
/opt/theos/bin/logify.pl /Users/lixingle/Desktop/test/result/Headers/BaseMsgContentViewController.h > /Users/lixingle/Desktop/test/result/Tweak/WXRedTweak/wxredtweak/Tweak.xm
執(zhí)行完以上命令,查看Tweak.xm文件就會發(fā)現(xiàn)已經(jīng)給BaseMsgContentViewController的所有方法添加上了log

**1.1 **刪除影響編譯的函數(shù)
Tweak項(xiàng)目執(zhí)行make命令,發(fā)現(xiàn)那個函數(shù)影響編譯先刪除。
最終刪除的函數(shù)有

**1.2 ** 編譯安裝到越獄手機(jī):

1.3 在越獄手機(jī)上查看log
這里需要另一個手機(jī)給這臺越獄手機(jī)發(fā)紅包,有女票的可以用女票的手機(jī),沒有的用別人女票的,像我這樣都沒有的只能注冊兩個微信號了。
- 在越獄手機(jī)終端執(zhí)行查看log命令
tail -f /var/log/syslog | grep WeChat - 用另一個手機(jī)給這臺越獄手機(jī)發(fā)送一個紅包或者文本消息,
- 多次嘗試上一步,查看log有沒有規(guī)律
經(jīng)過多次分析 — 刪除無用函數(shù) — 編譯安裝 — 再分析的過程,最后只剩下幾個和message相關(guān)的函數(shù)

根據(jù)常識定位到與消息響應(yīng)相關(guān)的方法:
-(void)addMessageNode:(id)arg1 layout:(_Bool)arg2 addMoreMsg:(_Bool)arg3 { %log; %orig; }
四、 查找全局接收消息的方法
我們知道了聊天頁面響應(yīng)消息的方法,下面就需要分析這個方法的調(diào)用堆棧,找到那個最頂層接收消息的方法。猜測可能是通知、代理、監(jiān)聽或者管理中心之類機(jī)制會在消息到來時(shí)告知聊天控制器。那我們應(yīng)該怎么分析呢?答案就是通過lldb動態(tài)調(diào)試。
1. lldb 調(diào)試準(zhǔn)備
1.1 在越獄手機(jī)終端中輸入
debugserver *:9527 -a "WeChat"

1.2 重新打開一個Mac終端,進(jìn)入lldb后執(zhí)行
process connect connect://192.168.220.195:9527

2. 分析addMessageNode函數(shù)調(diào)用堆棧
給-(void)addMessageNode:(id)arg1 layout:(_Bool)arg2 addMoreMsg:(_Bool)arg3方法添加斷點(diǎn),首先要找到該方法的內(nèi)存地址也就是該函數(shù)偏移后的基地址。
偏移后的基地址=偏移前基地址+指令所在模塊的ASLR偏移
**2.1 ** 查找ASLR偏移地址
在lldb中輸入image list -o -f命令,執(zhí)行結(jié)果如下,第一個就是ASLR地址:0x94000

2.2 查看偏移前函數(shù)基地址
打開IDA或者Hopper,搜索addMessageNode,就可以找到。我用的是IDA反匯編微信的Mach-O文件。下圖中標(biāo)紅的位置就是該函數(shù)偏移前的基地址:0x0000000101FA957C

所以addMessageNode的內(nèi)存偏移后地址 = 0x101FA957C + 0x94000 = 0x10203D57C
2.3 添加斷點(diǎn)
lldb中在0x10203D57C處添加斷點(diǎn)br s -a 0x10203D57C,添加成功后,給微信發(fā)送一條信息看能不能進(jìn)斷點(diǎn)。
發(fā)送一條消息果然進(jìn)斷點(diǎn)了,這時(shí)執(zhí)行bt命令查看調(diào)用堆棧如下:

2.4 依次把上圖中的fame#0 - frame4對應(yīng)地址翻譯成函數(shù)名
偏移前基地址 = 偏移后地址 - ASLR偏移地址
frame #0 = 0x000000010203d57c - 0x94000 = 0x101FA957C
frame #1 = 0x00000001022cc920 - 0x94000 = 0x102238920
frame #2 = 0x00000001022b4c38 - 0x94000 = 0x102220C38
frame #3 = 0x0000000104a479a0 - 0x94000 = 0x1049B39A0
frame #4 = 0x0000000102b661dc - 0x94000 = 0x102AD21DC
在lldb中依次查看偏移前基地址對應(yīng)函數(shù)名
0x101FA957C:[BaseMsgContentViewController addMessageNode:layout:addMoreMsg:]
0x102238920:[BaseMsgContentLogicController DidAddMsg:]
0x102220C38:[BaseMsgContentLogicController OnAddMsg:MsgWrap:]
0x1049B39A0:MMCommon`_callExtension + 480 //擴(kuò)展函數(shù),排除
0x102AD21DC:[CMessageMgr MainThreadNotifyToExt:]
2.5 繼續(xù)查找堆棧中函數(shù)
依次在frame0 、1、2、3 、5處添加斷點(diǎn),查看當(dāng)退出聊天室在其他頁面時(shí)能否進(jìn)斷點(diǎn)。
經(jīng)過測試,只有frmae #5: 0x0000000102b661dc處設(shè)置的斷點(diǎn),無論在微信任何頁面收到紅包都能觸發(fā)斷點(diǎn)。

0x0000000102b661dc對應(yīng)的方法名我們已經(jīng)在上一步分析到是:[CMessageMgr MainThreadNotifyToExt:],到此我們初步分析到一個收到消息會調(diào)用的全局函數(shù)[CMessageMgr MainThreadNotifyToExt:]。
五、 分析CMessageMgr類
接下來我們需要分析CMessageMgr看這個名字像是一個消息管理中心,可能離我們要找的全局接收消息的函數(shù)不遠(yuǎn)了。
1. logify分析CMessageMgr
按照第三部logify.pl添加log的方式分析CMessageMgr類。這里我們不在贅述具體步驟。
經(jīng)過分析log,消息到來時(shí)按照先后順序會依次調(diào)用一下10個函數(shù),當(dāng)然并不是每一個都是接收消息方法。

根據(jù)函數(shù)名和程序員第七感我們著重分析以下四個函數(shù):
- (void)CheckMessageStatus:(id)arg1 Msg:(id)arg2 { %log; %orig; }
- (void)AsyncOnPreAddMsg:(id)arg1 MsgWrap:(id)arg2 { %log; %orig; }
- (void)AsyncOnAddMsg:(id)arg1 MsgWrap:(id)arg2 { %log; %orig; }
- (void)AsyncOnPushMsg:(id)arg1 { %log; %orig; }
1.1 當(dāng)每次進(jìn)聊天室時(shí)都會調(diào)用CheckMessageStatus,所以排除它。
1.2 AsyncOnPreAddMsg、AsyncOnAddMsg、AsyncOnPushMsg這三個方法都是在消息到來時(shí)觸發(fā),從
方法命名AsyncOnPreAddMsg應(yīng)該是消息的前置處理,AsyncOnPushMsg可能是往隊(duì)列里面添加消息。
這三個方法都可以用來備選,我們先選AsyncOnAddMsg。
2. 分析AsyncOnAddMsg
%hook CMessageMgr
- (void)AsyncOnAddMsg:(id)arg1 MsgWrap:(id)arg2 {
NSLog(@"arg1 = %@ , arg2 = %@", arg1, arg2);
NSLog(@"arg1 class = %@ , arg2 class = %@", [arg1 class], [arg2 class]);
%orig;
}
%end
log輸出如下:

arg1:NSString
arg2: CMessageWrap
我們也能在頭文件中找到CMessageWrap同名的頭文件,所以函數(shù)聲明如下:
- (void)AsyncOnAddMsg:(NSString *)wxid MsgWrap:(CMessageWrap *)wrap;
3. 分析CMessageWrap消息內(nèi)容相關(guān)的類
經(jīng)過分析CMessageWrap頭文件和查看不同消息類型的log輸出,Tweak最終修改如下:
@interface CMessageWrap
@property (nonatomic, strong) NSString* m_nsContent;
@property (nonatomic, assign) NSInteger m_uiMessageType;
@property(retain, nonatomic) NSString *m_nsFromUsr;
@property(retain, nonatomic) NSString *m_nsToUsr;
@property(retain, nonatomic) NSString *m_nsAtUserList;
@property(retain, nonatomic) NSString *m_nsBizChatId;
@property(retain, nonatomic) NSString *m_nsBizClientMsgID;
@property(retain, nonatomic) NSString *m_nsDisplayName;
@property(retain, nonatomic) NSString *m_nsKFWorkerOpenID;
@property(retain, nonatomic) NSString *m_nsMsgSource;
@property(retain, nonatomic) NSString *m_nsPattern;
@property(retain, nonatomic) NSString *m_nsPushContent;
@property(retain, nonatomic) NSString *m_nsRealChatUsr;
@end
%hook CMessageMgr
- (void)AsyncOnAddMsg:(NSString *)wxid MsgWrap:(CMessageWrap *)wrap {
%orig;
NSInteger uiMessageType = [wrap m_uiMessageType];
NSString* content = [wrap m_nsContent];
NSString* nsFromUsr = [wrap m_nsFromUsr];
NSString* nsToUsr = [wrap m_nsToUsr];
NSString* nsAtUserList = [wrap m_nsAtUserList];
NSString* nsBizChatId = [wrap m_nsBizChatId];
NSString* nsBizClientMsgID = [wrap m_nsBizClientMsgID];
NSString* nsKFWorkerOpenID = [wrap m_nsKFWorkerOpenID];
NSString* nsMsgSource = [wrap m_nsMsgSource];
NSString* nsDisplayName = [wrap m_nsDisplayName];
NSString* nsPattern = [wrap m_nsPattern];
NSString* nsRealChatUsr = [wrap m_nsRealChatUsr];
NSString* nsPushContent = [wrap m_nsPushContent];
NSLog(@"m_uiMessageType=%zd m_nsContent=%@ m_nsFromUsr=%@ m_nsToUsr=%@ m_nsAtUserList=%@ m_nsBizChatId=%@ m_nsBizClientMsgID=%@ m_nsDisplayName=%@ m_nsKFWorkerOpenID=%@ m_nsMsgSource=%@ m_nsPattern=%@ m_nsPushContent=%@ m_nsRealChatUsr=%@",
uiMessageType,
content,
nsFromUsr,
nsToUsr,
nsAtUserList,
nsBizChatId,
nsBizClientMsgID,
nsDisplayName,
nsKFWorkerOpenID,
nsMsgSource,
nsPattern,
nsPushContent,
nsRealChatUsr);
//記錄消息
if( 1 == uiMessageType ){ //普通消息
if( 0 == nsPushContent.length){
if([nsToUsr rangeOfString:@"filehelper"].location != NSNotFound){
NSLog(@"[文件助手: %@]",content);
}else {
NSLog(@"[我: %@]",content);
}
}else{
NSLog(@"[%@]",nsPushContent);
}
}else if ( 3 == uiMessageType ){ //圖片消息
NSLog(@"收到圖片消息");
}else if ( 49 == uiMessageType ){ //紅包消息
NSLog(@"收到紅包消息");
}
}
%end
至此我們已經(jīng)找到了微信中全局接收消息的方法和紅包的類型類型uiMessageType = 49。接下來我們只需要找到搶紅包的方法,然后當(dāng)紅包消息到來時(shí)自動調(diào)用搶紅包方法就可以了。
何去何從
萬里長征我們邁出了第一步,更難的還在后邊。接下來我會用2篇文章講解怎么反匯編找到搶紅包的函數(shù),怎么在設(shè)置頁面添加一個自動搶紅包的開關(guān)以及微信的多開。敬請期待...
想及時(shí)獲得最新微信自動搶紅包文章,請關(guān)注微信公眾賬號:樂Coding,或者微信掃描下方二維碼。

icon.jpg