14. 項(xiàng)目實(shí)戰(zhàn)(微信搶紅包插件)
14.1 定位紅包消息響應(yīng)方法
14.1.1 砸殼和導(dǎo)出頭文件以供分析用
詳細(xì)過(guò)程見(jiàn)筆記第7章
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/749DC69A-3A8D-4B5C-9926-1220E69FC85F/WeChat.app/WeChat
class-dump -s -S -H WeChat.decrypted -o ./MyHeaders
14.1.2 借助cycript來(lái)動(dòng)態(tài)分析界面定位視圖控制器(ViewController)
- 打開(kāi)應(yīng)用并使用cycript附加進(jìn)程
cycript -p WeChat
- 使用recursiveDescription打印UIView對(duì)象
[[UIApp keyWindow] recursiveDescription].toString()
或者
UIApp.keyWindow.recursiveDescription().toString()
- 選擇一個(gè)UIView對(duì)象的id,借助nextResponder方法獲取視圖控制器
<UIView: 0x14eaed070; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x14eafa130>>
//使用nextResponder找到視圖控制器(ViewController)
cy# [#0x14eaed070 nextResponder]
#"<BaseMsgContentViewController: 0x14f1dc200>"
- 在class-dump到處的頭文件中姿骏,查找BaseMsgContentViewController相關(guān)的文件
? ls -al *BaseMsgContentViewController*
-rw-r--r-- 1 liuzhongzheng staff 26105 Aug 25 15:59 BaseMsgContentViewController.h
-
cycript的其它用法介紹
-
_printHierarchy - 直接打印所有UIViewController
[[[UIWindow keyWindow] rootViewController] _printHierarchy].toString()
_autolayoutTrace - recursiveDescription的簡(jiǎn)化版甩栈,去掉了UIView的一些描述
[[UIApp keyWindow] _autolayoutTrace].toString()
- 獲取bundle info
[[NSBundle mainBundle] infoDictionary].toString()
-
14.1.3 借助Reveal來(lái)動(dòng)態(tài)分析界面定位視圖控制器(ViewController)
OSX 上安裝Reveal
官網(wǎng):https://revealapp.com/iOS 上安裝Reveal Loader
通過(guò)Cydia搜索安裝Reveal Loader使用Reveal觀察界面結(jié)構(gòu)
14.1.4 借助thoes模塊Logify來(lái)精確定位消息響應(yīng)方法
創(chuàng)建Tweak工程
批量生成hook BaseMsgContentViewController.h文件中方法的Tweak.xm文件
/opt/theos/bin/logify.pl ../WeChat/Headers/BaseMsgContentViewController.h > Tweak.xm
用logify.pl生成的Tweak.xm文件替換Tweak工程中的Tweak.xm文件
編譯Tweak工程并安裝
make package
make install
- 通過(guò)查看和分析日志定位消息響應(yīng)的方法
tail -f /var/log/syslog | grep WeChat
[<BaseMsgContentViewController: 0x1368e9c00> addMessageNode:{m_uiMesLocalID=38, m_ui64MesSvrID=6021494297990342718, m_nsFromUsr=wxi*431~19, m_nsToUsr=wxi*r12~19, m_uiStatus=4, type=1, msgSource="<msgsource><sequence_id>649531066</sequence_id></msgsource>"} layout:1 addMoreMsg:0]
//定位到與消息響應(yīng)相關(guān)的方法
- (void)addMessageNode:(id)arg1 layout:(_Bool)arg2 addMoreMsg:(_Bool)arg3 { %log; %orig; }
14.1.5 借助lldb進(jìn)行動(dòng)態(tài)調(diào)試
- 在iOS上配置debugserver
- 把iPhone連接xcode尿孔,在iOS上/Developer/usr/bin/目錄下面生成調(diào)試工具debugserver
Flongers-iPhone:/Developer/usr/bin root# ls
DTDeviceArbitration* ScreenShotr* XcodeDeviceMonitor* debugserver* iprofiler* xctest*
-
給debugserver瘦身和重新簽名祠汇,并放入/usr/bin/目錄下方便隨時(shí)調(diào)用
lipo(靜態(tài)庫(kù)拆分與合并)命令的介紹: http://www.reibang.com/p/e590f041c5f6//拷貝到OS X上去 scp debugserver luz@192.168.1.138:/tmp //查看CPU架構(gòu)信息 ?lipo -info debugserver Architectures in the fat file: debugserver are: armv7 armv7s arm64 //瘦身 lipo -thin arm64 debugserver -output debugserver.thin //使用ldid添加task_for_pid權(quán)限 ? ldid -Sent.xml debugserver.thin ? ldid -e debugserver.thin <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.springboard.debugapplications</key> <true/> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> <key>run-unsigned-code</key> <true/> </dict> </plist> //使用codesign添加task_for_pid權(quán)限 codesign -s - --entitlements ent.plist -f debugserver.thin ? ldid -e debugserver.thin <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.springboard.debugapplications</key> <true/> <key>run-unsigned-code</key> <true/> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> </dict> </plist>
-
在iOS使用debugserver附加程序
- 打開(kāi)調(diào)試
debugserver -x backboard IP:port /path/to/executable
- 附加調(diào)試
debugserver *:9527 -a "WeChat"
在OSX使用lldb連接debugserver進(jìn)行調(diào)試
lldb
process connect connect://192.168.1.113:9527 //ip是iPhone的地址,端口要一致
- 使用usbmuxd工具通過(guò)USB口轉(zhuǎn)發(fā)ssh和調(diào)式信息
- 通過(guò)USB口轉(zhuǎn)發(fā)ssh
//把本地2222端口轉(zhuǎn)發(fā)到iOS的22(ssh)端口
./tcprelay.py -t 22:2222
Forwarding local port 2222 to remote port 22
//ssh進(jìn)行連接 - localhost(127.0.0.1)是本地主機(jī)(本機(jī)的標(biāo)準(zhǔn)域名)
ssh root@localhost -p 2222
-
通過(guò)USB口轉(zhuǎn)發(fā)lldb調(diào)試
//把本地9527端口轉(zhuǎn)發(fā)到iOS的9527端口 ./tcprelay.py -t 9527:9527 //在iOS上指定debugserver監(jiān)聽(tīng)的端口號(hào)(要與9527:9527冒號(hào)前面的一致9527) debugserver *:9527 -a "WeChat" //在OSX上指定連接的端口號(hào)(要與9527:9527冒號(hào)后面的一致) lldb process connect connect://localhost:9527
查看ASLR偏移
image list -o -f
(lldb) image list -o -f
[ 0] 0x0000000000054000 /private/var/mobile/Containers/Bundle/Application/749DC69A-3A8D-4B5C-9926-1220E69FC85F/WeChat.app/WeChat(0x0000000100054000)
偏移后模塊基地址 = 偏移前模塊基地址 + 模塊的ASLR偏移
偏移后符號(hào)基地址 = 偏移前符號(hào)基地址 + 符號(hào)所在模塊的ASLR偏移(一般用在這兒)
偏移后指令基地址 = 偏移前指令基地址 + 指令所在模塊的ASLR偏移
- 使用Hopper查看函數(shù)基地址
-[BaseMsgContentViewController addMessageNode:layout:addMoreMsg:]:
0000000101dcbb0c db 0xe9 ; '.'
0000000101dcbb0d db 0x23 ; '#'
-
使用lldb的br命令設(shè)備斷點(diǎn)br
br s -a '0x0000000000054000+0x0000000101dcbb0c' 設(shè)置斷點(diǎn) b function br s –a address br s –a 'ASLROffset+address' //查看所有斷點(diǎn) br list //刪除斷點(diǎn) br delete 1 //程序繼續(xù)運(yùn)行
發(fā)送消息锨侯,等待觸發(fā)斷點(diǎn)斤斧,然后查看函數(shù)調(diào)用堆棧
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000101defb0c WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 23979876
frame #1: 0x0000000102035434 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 26361996
frame #2: 0x000000010202059c WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 26276340
frame #3:
WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 34703800
frame #4: 0x0000000187955f9c Foundation`__NSThreadPerformPerform + 372
frame #5: 0x0000000186a0c240 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
frame #6: 0x0000000186a0b4e4 CoreFoundation`__CFRunLoopDoSources0 + 264
frame #7: 0x0000000186a09594 CoreFoundation`__CFRunLoopRun + 712
frame #8: 0x00000001869352d4 CoreFoundation`CFRunLoopRunSpecific + 396
frame #9: 0x00000001901536fc GraphicsServices`GSEventRunModal + 168
frame #10: 0x000000018b4fafac UIKit`UIApplicationMain + 1488
frame #11: 0x00000001000bab5c WeChat`_mh_execute_header + 617308
frame #12: 0x00000001988a6a08 libdyld.dylib`start + 4
//計(jì)算函數(shù)地址
偏移后符號(hào)基地址 = 偏移前符號(hào)基地址 + 符號(hào)所在模塊的ASLR偏移
偏移后符號(hào)基地址 - 符號(hào)所在模塊的ASLR偏移 = 偏移前符號(hào)基地址
0x0000000101e3db24 - 00000000000f4000 = 101D49B24
-
在Hopper或者IDA中分析函數(shù)調(diào)用關(guān)系
0x0000000101dcbb0c -[BaseMsgContentViewController addMessageNode:layout:addMoreMsg:] //在此處設(shè)置斷點(diǎn),發(fā)現(xiàn)只有在當(dāng)前聊天界面才會(huì)觸發(fā)递胧,不是我們找的目標(biāo)方法 0x102011434 = 0x0000000102035434 - 0x0000000000024000 -[BaseMsgContentLogicController DidAddMsg:] a //在此處設(shè)置斷點(diǎn)碑韵,發(fā)現(xiàn)只有在當(dāng)前聊天界面才會(huì)觸發(fā),不是我們找的目標(biāo)方法 0x101FFC59C = 0x000000010202059c - 0x0000000000024000 -[BaseMsgContentLogicController OnAddMsg:MsgWrap:] //在此處設(shè)置斷點(diǎn)缎脾,只要收到消息就會(huì)觸發(fā)祝闻,符合我們尋址的目標(biāo)方法 0x102805D60 = 0x0000000102829d60 - 0x0000000000024000 -[CMessageMgr MainThreadNotifyToExt:] 注:定位到另一個(gè)與消息接收有關(guān)的類(lèi)CMessageMgr,在微信到處頭文件中發(fā)現(xiàn)有同名文件CMessageMgr.h
-
繼續(xù)使用Tweak來(lái)hook CMessageMgr中的方法遗菠,觀察日志輸出
//使用logify來(lái)繼續(xù)追蹤C(jī)MessageMgr這個(gè)類(lèi)联喘,發(fā)現(xiàn)消息到來(lái)時(shí),這幾個(gè)方法有響應(yīng) //單獨(dú)分析這幾個(gè)方法 %hook CMessageMgr - (void)AsyncOnPreAddMsg:(id)arg1 MsgWrap:(id)arg2 { %log; %orig; } - (void)AsyncOnAddMsg:(id)arg1 MsgWrap:(id)arg2 { %log; %orig; } - (void)AsyncOnPushMsg:(id)arg1 { %log; %orig; } - (void)CheckMessageStatus:(id)arg1 Msg:(id)arg2 { %log; %orig; } %end 1.打開(kāi)聊天界面時(shí)CheckMessageStatus有反應(yīng)舷蒲,所以排除它 2.AsyncOnPreAddMsg耸袜、AsyncOnAddMsg、AsyncOnPushMsg這三個(gè)方法都是在消息到來(lái)時(shí)觸發(fā)牲平,從 方法命名AsyncOnPreAddMsg應(yīng)該是消息的前置處理堤框,AsyncOnPushMsg可能是往隊(duì)列里面添加消息。 這三個(gè)方法都可以用來(lái)備選纵柿,我們先選AsyncOnAddMsg蜈抓。 //分析AsyncOnAddMsg的參數(shù) %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 //參數(shù)輸出日志如下 Sep 9 16:39:53 Flongers-iPhone WeChat[16048]: arg1 = wxid_lx85gyfaib9431 , arg2 = {m_uiMesLocalID=61, m_ui64MesSvrID=6890149828648546534, m_nsFromUsr=wxi*431~19, m_nsToUsr=wxi*r12~19, m_uiStatus=3, type=1, msgSource="<msgsource><sequence_id>649531092</sequence_id></msgsource>"} Sep 9 16:39:53 Flongers-iPhone WeChat[16048]: arg1 = __NSCFString , arg2 = CMessageWrap //所以消息響應(yīng)函數(shù)如下,CMessageWrap亦有同名的導(dǎo)出頭文件CMessageWrap.h - (void)AsyncOnAddMsg:(NSString *)wxid MsgWrap:(CMessageWrap *)wrap;
分析消息內(nèi)容相關(guān)的類(lèi)CMessageWrap
@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; // @synthesize m_nsAtUserList;
@property(retain, nonatomic) NSString *m_nsBizChatId; // @synthesize m_nsBizChatId;
@property(retain, nonatomic) NSString *m_nsBizClientMsgID; // @synthesize m_nsBizClientMsgID;
@property(retain, nonatomic) NSString *m_nsDisplayName; // @synthesize m_nsDisplayName;
@property(retain, nonatomic) NSString *m_nsKFWorkerOpenID; // @synthesize m_nsKFWorkerOpenID;
@property(retain, nonatomic) NSString *m_nsMsgSource; // @synthesize m_nsMsgSource;
@property(retain, nonatomic) NSString *m_nsPattern; // @synthesize m_nsPattern;
@property(retain, nonatomic) NSString *m_nsPushContent; // @synthesize m_nushContent;
@property(retain, nonatomic) NSString *m_nsRealChatUsr; // @synthesize 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