花費(fèi)了很多天的原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處https://yohunl.com/ding-ding-qiang-hong-bao-cha-jian-iospian/ ,謝謝!
網(wǎng)絡(luò)上關(guān)于微信紅包的分析文章已經(jīng)非常多了,基本上照著做就可以弄個(gè)微信搶紅包插件出來,不過,隨著阿里巴巴的釘釘在企業(yè)中的流行,很多企業(yè)現(xiàn)在都采用釘釘來辦公了,順帶著,也就使用釘釘來發(fā)紅包了,學(xué)習(xí)了那么多逆向的理論后,需要拿一個(gè)東西來練練手,剛好,釘釘就符合這個(gè)要求,于是,便有了下面的這篇文章.套用騰訊的何兆林在文章 移動(dòng)App入侵與逆向破解技術(shù)-iOS篇說的一段話
"破解有時(shí)候很耗時(shí)妓笙,和程序開發(fā)正好相反,它耗時(shí)不是耗在寫代碼上椎侠,而是耗在尋找注入點(diǎn)和逆向工程上姿染,有可能你花了3天時(shí)間去找程序的破綻伐憾,但是最終的破解代碼可能就2行勉痴,不到一分鐘就搞定了;但是你也需要做好面對(duì)失敗的準(zhǔn)備树肃,如果路選錯(cuò)了蒸矛,有可能你這3天完全是在浪費(fèi)腦細(xì)胞"
本文的源碼放在我的gitHub上 DingTalkNoJailTweak ,,其ReadMe文件說明了其中相關(guān)的代碼是起什么作用的.
當(dāng)然了,這篇文件會(huì)涉及比較多的逆向的東西,這些理論等前序知識(shí),可以參考網(wǎng)上的,網(wǎng)上的資料比較多了,比如TheOS的開發(fā)環(huán)境的搭建,Hopper逆向分析軟件的使用,ida Pro等,移動(dòng)App入侵與逆向破解技術(shù)-iOS篇上就有很不錯(cuò)的前序理論知識(shí)的介紹.
好了,閑話不多說,直接進(jìn)入正題.
相關(guān)工具和環(huán)境
一臺(tái)越獄的手機(jī),雖然不越獄的手機(jī)也可以用來分析,但是,那樣將會(huì)大大的減慢整個(gè)分析過程,因?yàn)榉窃姜z的只能通過注入后打log日志來分析,耗時(shí)耗力,所以,越獄手機(jī)是必備
越獄的手機(jī)上安裝了
cycript
從cydia中安裝
FlexInjected
從cydia中安裝,在cydia中搜索Flipboard FLEX loader
clutch
https://github.com/KJCracks/Clutch clutch下載
下載后,按照上面的指示,
Clone the repo
git clone git@github.com:KJCracks/Clutch.git
cd Clutch
Build
xcodebuild -project Clutch.xcodeproj -configuration Release ARCHS="armv7 armv7s arm64" build
Install
scp /path/to/Clutch root@<your.device.ip>:/usr/bin/
ssh root@<your.device.ip>
當(dāng)然你可以使用同步助手,PP助手,iFunBox,iTools Pro來安裝到手機(jī)的/usr/bin下
如果不是用SSH從電腦上登陸手機(jī)的命令行的話,你可以cydia下載MTerminal,然后使用su切換用戶到root,再使用命令 chmod +x clutch來提升權(quán)限
MTerminal
cydia中下載,可以方便的在你的手機(jī)上使用終端命令行.
一臺(tái)MAC電腦安裝了
class-dump
class-dump,用來dump出二進(jìn)制的頭文件.
Theos
可以參考我的另外一篇文章來安裝 iOS 越獄的Tweak開發(fā)
Hopper Disassembler v3 或者ida Pro
用來進(jìn)行匯編分析的
xcode
insert_dylib
iTools Pro
iTools
方便的用來拷貝.瀏覽手機(jī)上的所有文件
砸殼
砸殼,是為了能夠使用class-dump來導(dǎo)出所有的頭文件,有了頭文件,我們分析起來才是有可能的.
ssh連上你的越獄手機(jī)/或者像我一樣直接在手機(jī)上的Terminal中操作,調(diào)用clutch來進(jìn)行砸殼
YohunlIp6:~ root# clutch
Usage: clutch [OPTIONS]
-b --binary-dump <value> Only dump binary files from specified bundleID
-d --dump <value> Dump specified bundleID into .ipa file
-i --print-installed Print installed applications
--clean Clean /var/tmp/clutch directory
--version Display version and exit
-? --help Display this help and exit
-n --no-color Print with colors disabled
-v --verbose Print verbose messages
以上是clutch支持的命令
其中的 -b就是 用來砸殼的,需要一個(gè)參數(shù)是要被砸殼的應(yīng)用的bundleid,獲取釘釘?shù)腷undleid有很多方法,你可以直接使用 clutch -i 輸出手機(jī)上所有已經(jīng)安裝的應(yīng)用,找到bundleid.釘釘?shù)腷undleid是com.laiwang.DingTalk
開始砸殼
稍等一會(huì),砸殼完畢,會(huì)輸出砸完殼后的存放路徑
使用iTool Pro/PP助手等工具,將砸完殼的釘釘拷貝到電腦上
導(dǎo)出頭文件
在電腦上,將砸殼后的文件中讀取出頭文件, DingTalk是從砸殼后的ipa包中提取出來的釘釘?shù)目蓤?zhí)行文件,這個(gè)文件比較好找,一般都是沒有后綴的,大小最大的那個(gè).
class-dump -H DingTalk -o DingTalkHeaders
class-dump的簡(jiǎn)單使用方式
最簡(jiǎn)單的使用方式
class-dump -H DDMMerchant -o yicommonHeaders
class-dump -H Payload/WeChat.app/WeChat -o wechatHeaders
還可以加上 -S -s來對(duì)生成的方法,類都進(jìn)行排序
class-dump -S -s -H Payload/WeChat.app/WeChat -o wechatHeaders
一定要寫 -H,不然,都是在命令行輸出的,不會(huì)將內(nèi)容輸入到文件夾下!!
導(dǎo)出頭文件后,新建一個(gè)xcode工程,將所有的頭文件都導(dǎo)出到工程中,為什么做這一步呢,因?yàn)?Xcode的搜索能力還是不錯(cuò)的,方便我們?nèi)ゲ榭催@些頭文件.當(dāng)然,由于一次性導(dǎo)入xcode工程的文件量比較大,在導(dǎo)入的過程中,xcode可能會(huì)假死,稍微耐心一點(diǎn).
開始分析
在這里,強(qiáng)烈的推薦Flipboard的FLEX,這個(gè)簡(jiǎn)直就是iOS分析界的神器啊.以前還要用命令才能一步步的將界面所對(duì)應(yīng)的視圖,以及相應(yīng)的ViewController分析出來,現(xiàn)在有了它,這個(gè)過程可以大大加速了.
如果你安裝了FlexInjected,打開設(shè)置那里FlexInjected中釘釘?shù)拈_關(guān),這樣,當(dāng)釘釘啟動(dòng)的時(shí)候,就加載了Flex了,加載了Flex后,會(huì)在釘釘中出現(xiàn)Flex的界面,點(diǎn)擊界面可以進(jìn)行相應(yīng)的操作.
首先,第一步,我們先要找到搶紅包的視圖所對(duì)應(yīng)的對(duì)象以及相應(yīng)的VC,這個(gè)事情利用Flex還是很簡(jiǎn)單的.
設(shè)置中打開 FlexInjected中的釘釘,這樣,釘釘啟動(dòng)的時(shí)候,就會(huì)加載FLex.
打開釘釘,就會(huì)出現(xiàn)Flex的菜單了,Flex的基本操作,可以參考FLex官網(wǎng)的.
通過Flex,我們很容易的得到下面的結(jié)果
1 聊天頁面的紅包視圖的View是DTMessageBubbleRedEnvelopView
2 點(diǎn)擊后,搶紅包的頁面的視圖是
DTOpenLuckyMoneyView
----DTOpenLuckyMoneyEntityView
3 所有的聊天的會(huì)話的控制器是DTConversationListViewController.
我們已經(jīng)知道了搶紅包的關(guān)鍵視圖是DTOpenLuckyMoneyEntityView 和DTOpenLuckyMoneyView.打開我們dump出來的所有的頭文件,查看DTOpenLuckyMoneyEntityView頭文件定義
DTOpenLuckyMoneyEntityView的定義(截取我們需要的關(guān)鍵部分)
@interface DTOpenLuckyMoneyEntityView : UIView <DTUserNameLabelDelegate>{
id <DTOpenLuckyMoneyEntityViewDelegate> _delegate;
....
}
@property(nonatomic) __weak id <DTOpenLuckyMoneyEntityViewDelegate> delegate;
- (void)didClickOpenLuckyMoneyBtn:(UIButton *)arg1;
......
@end
DTOpenLuckyMoneyEntityViewDelegate
@protocol DTOpenLuckyMoneyEntityViewDelegate <NSObject>
- (void)didClickViewMore:(DTOpenLuckyMoneyEntityView *)arg1;
- (void)didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
@end
DTOpenLuckyMoneyView的定義(截取我們需要的關(guān)鍵部分)
@interface DTOpenLuckyMoneyView : UIView <DTOpenLuckyMoneyEntityViewDelegate>
//通過此方法,構(gòu)造出來視圖
+ (void)showLuckyMoneyWithPickingStatus:(DTBizRedEnvelopClusterPickingStatus *)arg1 withController:(DTMessageOTOViewController *)arg2 delegate:(id <DTOpenLuckyMoneyViewDelegate>)arg3;
- (void)didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
@end
通過上面的兩個(gè)類和一個(gè)協(xié)議的方法定義,我們很容易的就可以得出它們之間的關(guān)系大概是如下這樣的
當(dāng)用戶點(diǎn)擊了拆紅包按鈕時(shí)
DTOpenLuckyMoneyEntityView中的button的響應(yīng)事件
- (void)didClickOpenLuckyMoneyBtn:(id)arg1;
其中 先獲取id <DTOpenLuckyMoneyEntityViewDelegate> _delegate; (取值是 DTOpenLuckyMoneyView (@interface DTOpenLuckyMoneyView : UIView <DTOpenLuckyMoneyEntityViewDelegate>))
轉(zhuǎn)給其方法 - (void)didClickViewMore:(DTOpenLuckyMoneyEntityView *)arg1;
也就是
DTOpenLuckyMoneyEntityView
- (void)didClickOpenLuckyMoneyBtn:(id)sender {
[self.delegate didClickOpenLuckyMoney:self];//_delegate; (取值是 DTOpenLuckyMoneyView
}
這只是大概的流程,我們可以進(jìn)一步細(xì)化這個(gè)流程,這個(gè)時(shí)候就要用到Hopper Disassembler或者iDA Pro了,用Hopper打開釘釘?shù)目蓤?zhí)行文件,等待分析完畢,打開DTOpenLuckyMoneyView的didClickViewMore實(shí)現(xiàn),(這里我只截取了部分)
從這個(gè)方法的匯編代碼中,我們大體上可以得到如下的信息:
DTOpenLuckyMoneyView didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
[self statusModel]; //DTBizRedEnvelopClusterPickingStatus *statusModel,其中有一個(gè)屬性是DTBizRedEnvelopCluster,包含了紅紅包的相關(guān)信息,看上面的截圖
[self utOpenRedEnvelop:statusModel]
[self beginLoading]//其中會(huì)調(diào)用DTOpenLuckyMoneyEntityView的loading方法等
[DTRedEnvelopServiceFactory defaultServiceIMP]獲取一個(gè) DTRedEnvelopServiceIMP
self redEnvelopCluster
serverFormatWithCountryCode:number:
clusterId
luckyMoneyEntityView
//DTRedEnvelopServiceIMP - (void)pickRedEnvelopCluster:(long long)arg1 clusterId:(id)arg2 successBlock:(CDUnknownBlockType)arg3 failureBlock:(CDUnknownBlockType)arg4;
pickRedEnvelopCluster:clusterId:successBlock:failureBlock:
我們看到其中有 DTRedEnvelopServiceFactory這個(gè)類,還有pickRedEnvelopCluster方法,在頭文件中搜索pickRedEnvelopCluster,可以得到
我們又得到了幾個(gè)關(guān)聯(lián)的類,尤其是其中的DTRedEnvelopService.
到這里,你可能還是很迷惑,下步該做什么?
不要緊,TheOS給我們提供了一個(gè)logify.pl腳本(theos/bin/logify),這個(gè)腳本可以將一個(gè)類中的所有方法都加上日志.
大概的使用方式就是
$THEOS/bin/logify.pl ./DTMessageMTMViewController.h 在終端顯示結(jié)果
$THEOS/bin/logify.pl ./DTMessageMTMViewController.h > /out/to/DTMessageMTMViewController.xm
我們使用Theos建立一個(gè)tweak,這個(gè)tweak的作用就是輸出日志,有關(guān)theos的安裝和使用,可以參考我的另外一篇博客iOS 越獄的Tweak開發(fā),我們將我們覺得可疑的類都加上日志,編譯一個(gè)tweak,安裝到手機(jī)上.
你的這個(gè)tweak.xm文件中的內(nèi)容應(yīng)該是大致如下
%hook DTOpenLuckyMoneyView
+ (void)showLuckyMoneyWithPickingStatus:(NSObject *)arg1 withController:(id)arg2 delegate:(id)arg3
{
NSLog(@"WithPickingStatus = %@,className = %@",arg1,NSStringFromClass(arg1.class));
%log; %orig;
}
+ (void)queryAndOpenPageWithClusterId:(id)arg1 senderId:(long long)arg2 withController:(id)arg3 { %log; %orig; }
........
%end
接著,你在釘釘中,讓別人發(fā)一個(gè)紅包,然后你點(diǎn)擊拆紅包,拆掉紅包,將整個(gè)的Log輸出,復(fù)制出來,用于分析.
這里,我將某一次的日志部分放出來,讓你對(duì)這個(gè)日志有個(gè)概念
<Warning>: <L_UI> -[DTMessageBubbleTapHandler messageBubbleViewCell:didTappedWithGestureRecognizer:] #81 [INFO] tap msg mid = 12685858885 , msgType = 902
[m +[<DTRedEnvelopServiceFactory: 0x103303bd8> defaultServiceIMP]
[m -[<DTRedEnvelopServicePersistenceIMP: 0x14de243b0> initWithDbConnection:<OpenDatabase: 0x14f247e00>]
[m = <DTRedEnvelopServicePersistenceIMP: 0x14de243b0>
[m +[<DTRedEnvelopServiceFactory: 0x103303bd8> createServiceIMPWithPersistence:<DTRedEnvelopServicePersistenceIMP: 0x14de243b0> network:<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0>]
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> init]
[m = <DTRedEnvelopServiceIMP: 0x14fec6a70>
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> setPersistenceIMP:<DTRedEnvelopServicePersistenceIMP: 0x14de243b0>]
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> setNetworkIMP:<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0>]
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> sendInitAlipay]
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> getBindAlipaySuccessBlock:(null) failureBlock:(null)]
[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> networkIMP]
[m = 0x<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0>
[m -[<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0> getBindAlipaySuccessBlock:<__NSStackBlock__: 0x16fdb5310> failureBlock:<__NSStackBlock__: 0x16fdb52e8>]
[m = <DTRedEnvelopServiceIMP: 0x14fec6a70>
[m = <DTRedEnvelopServiceIMP: 0x14fec6a70>
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:239[m [0;30;46mDEBUG:[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> checkRedEnvelopClusterPickingStatus:88160518 clusterId:5PnAJfRG successBlock:<__NSStackBlock__: 0x16fdb5638> failureBlock:<__NSStackBlock__: 0x16fdb5608>]
<Warning>: <L_LWP> -[LWPTransactionService enqueue:] #154 [Info] enqueue uri=/r/Adaptor/DingPayI/getBindAlipay [mid:1df66b00]
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:211[m [0;30;46mDEBUG:[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> networkIMP]
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:211[m [0;30;46mDEBUG:[m = 0x<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0>
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:265[m [0;30;46mDEBUG:[m -[<DTRedEnvelopServiceNetworkIMP: 0x14fa743c0> checkRedEnvelopClusterPickingStatus:88160518 clusterId:5PnAJfRG successBlock:<__NSStackBlock__: 0x16fdb5518> failureBlock:<__NSStackBlock__: 0x16fdb54e0>]
<Warning>: <L_LWP> -[LWPMessenger lwpConnection:willSendMessage:isFirstMessage:] #1075 [Info] willSend [0][[id:85b80100(addr:0x14f2461c0, idx:0) - Master]]: 1df66b00 0
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:172[m [0;30;46mDEBUG:[m +[<DTRedEnvelopPickIService: 0x1033227b8> _serviceKey__]
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:172[m [0;30;46mDEBUG:[m = Adaptor/RedEnvelopPickIService
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:171[m [0;30;46mDEBUG:[m +[<DTRedEnvelopPickIService: 0x1033227b8> _appname__]
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:171[m [0;30;46mDEBUG:[m = (null)
<Warning>: <L_LWP> -[LWPTransactionService enqueue:] #154 [Info] enqueue uri=/r/Adaptor/RedEnvelopPickI/checkRedEnvelopClusterPickingStatus [mid:7e426c00]
<Warning>: <L_LWP> -[LWPMessenger lwpConnection:willSendMessage:isFirstMessage:] #1075 [Info] willSend [0][[id:85b80100(addr:0x14f2461c0, idx:0) - Master]]: 7e426c00 0
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:206[m [0;30;46mDEBUG:[m -[<DTRedEnvelopServiceIMP: 0x14fec6a70> setBindAlipayAccount:yoh***@163.com]
<Warning>: <L_LWP> -[LWPTransactionService destoryV2:withError:withResponse:] #258 [Info] destory: mid:1df66b00 200
<Warning>: <L_LWP> -[LWPTransactionService destoryV2:withError:withResponse:] #258 [Info] destory: mid:7e426c00 200
<Warning>: __98-[DTRedEnvelopServiceIMP checkRedEnvelopClusterPickingStatus:clusterId:successBlock:failureBlock:]_block_invoke #280 [INFO] clusterId=(null), flowCount=0 pickMoney=0 pickstatus=0
<Warning>: WithPickingStatus = <DTBizRedEnvelopClusterPickingStatus: 0x14deac5a0>,className = DTBizRedEnvelopClusterPickingStatus
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:109[m [0;30;46mDEBUG:[m +[<DTOpenLuckyMoneyView: 0x1032edd10> showLuckyMoneyWithPickingStatus:<DTBizRedEnvelopClusterPickingStatus: 0x14deac5a0> withController:<DTMessageOTOViewController: 0x14eb6d400> delegate:<DTMessageOTOViewController: 0x14eb6d400>]
<Notice>: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:116[m [0;30;46mDEBUG:[m +[<DTOpenLuckyMoneyView: 0x1032edd10> viewWithDelegate:<DTMessageOTOViewController: 0x14eb6d400>]
當(dāng)然了,這個(gè)分析日志的過程,你可能會(huì)持續(xù)非常多的次數(shù),因?yàn)樵诜治龅倪^程中,你可能會(huì)發(fā)現(xiàn)另外的某個(gè)對(duì)象可能是你要的分析,你就要再修改tweak.xm文件中的hook,將這個(gè)對(duì)象的方法全hook住,反反復(fù)復(fù)
這個(gè)時(shí)候,你應(yīng)該能夠得到如下的關(guān)鍵信息
<DTRedEnvelopServiceFactory: 0x103303bd8> defaultServiceIMP]
DTRedEnvelopServiceFactory通過方法defaultServiceIMP構(gòu)造了一個(gè)DTRedEnvelopServiceIMP對(duì)象,然后通過這個(gè)DTRedEnvelopServiceIMP對(duì)象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法去拆紅包
<DTRedEnvelopServiceFactory: 0x103303bd8> defaultServiceIMP]
DTRedEnvelopServiceIMP - (void)pickRedEnvelopCluster:(long long)arg1 clusterId:(id)arg2 successBlock:(CDUnknownBlockType)arg3 failureBlock:(CDUnknownBlockType)arg4;
這個(gè)方法就是最后的拆紅包功能的方法,到此
拆紅包這個(gè)就很簡(jiǎn)答了,由于[DTRedEnvelopServiceFactory defaultServiceIMP]是一個(gè)類方法,所以不需要我們構(gòu)造的,那么現(xiàn)在的問題是,在[DTRedEnvelopServiceFactory defaultServiceIMP]周后,有沒有調(diào)用其它的方法來配置這個(gè)構(gòu)造出來的DTRedEnvelopServiceIMP對(duì)象呢?
我們可以繼續(xù)根據(jù)輸出的日志來分析....
這里,我直接告訴你分析的結(jié)果
[DTRedEnvelopServiceFactory defaultServiceIMP]方法中,會(huì)初始化一個(gè)全局的DTRedEnvelopServiceIMP對(duì)象.并且配置好它,例如綁定支付信息等等 (你可以從匯編中大概看一下).返回給的DTRedEnvelopServiceIMP對(duì)象就是一個(gè)完全可用的對(duì)象了,只要調(diào)用這個(gè)對(duì)象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法,就可以完成拆紅包這個(gè)動(dòng)作了.
那么,自動(dòng)拆紅包這個(gè),就變?yōu)槟阍趺茨玫竭@個(gè)方法所需的4個(gè)參數(shù)了,后兩個(gè)block,一看就知道可以傳nil的,因?yàn)槲覀儗?duì)成功失敗的回調(diào)不感興趣,問題就變?yōu)樵趺茨玫角皟蓚€(gè)參數(shù)了.
第一個(gè)參數(shù)是一個(gè) long long類型,第二個(gè)是一個(gè) NSString.
FLex有一個(gè)非常好用的功能,是其中帶有的Heap Objects,這個(gè)可以用來查看當(dāng)前內(nèi)存中有哪些方法.結(jié)合我們上面的日志分析,會(huì)發(fā)現(xiàn)
DTOpenLuckyMoneyEntityView 對(duì)象中的方法 - (DTBizRedEnvelopCluster *)currentCluster;
其中包含了我們需要的紅包的相關(guān)信息
接下來,我們讓別人再發(fā)一個(gè)紅包,然后,點(diǎn)擊出現(xiàn)拆紅包視圖,這個(gè)時(shí)候,利用Flex的Heap Objects查內(nèi)存中的DTBizRedEnvelopCluster方法,從中看到@property(copy, nonatomic) NSString *clusterId;和@property(nonatomic) long long sender,記下來
再搜尋 DTRedEnvelopServiceIMP對(duì)象,Flex可以直接調(diào)用搜尋出來的某個(gè)對(duì)象,在Flex中直接調(diào)用方法pickRedEnvelopCluster,傳遞給其,注意,這里在Flex中第二個(gè)參數(shù)是字符串,使用""而不是@"".
點(diǎn)擊右上角的call,調(diào)用這個(gè)方法,不出意外,你會(huì)發(fā)現(xiàn),紅包已經(jīng)被拆了,到此,驗(yàn)證了,的確是這個(gè)方法,而且也知道了參數(shù)怎么拿到的.
OK,問題又來了,如果沒有出現(xiàn)拆紅包視圖,那么就沒有這個(gè)DTBizRedEnvelopCluster類呀,那怎么獲取其中的兩個(gè)參數(shù)出來呢?
這的確是個(gè)問題,你要再回到匯編和頭文件中,去分析了....
每當(dāng)來一個(gè)新消息,消息的列表頁面就已經(jīng)收到了,這說明在這個(gè)頁面的時(shí)候,應(yīng)該已經(jīng)獲取到了搶紅包所需要的相關(guān)信息了.我們接下來找到他們之間的聯(lián)系
我們平時(shí)看到的聊天的集合信息頁是 DTConversationListController,瀏覽頭文件,看到DTConversationListDataSource *_dataSource;
進(jìn)入DTConversationListDataSource,看到其中有
@property(retain, nonatomic) NSMutableArray<DTBizConversation * WKBizConversation *等> *conversationList
從Heap Object進(jìn)去,查看這個(gè)conversationList對(duì)象中的內(nèi)容,可以看到其中存放的大概是DTBizConversation或者是WKBizConversation,繼續(xù)瀏覽
我們看到DTBizConversation中,有一個(gè)對(duì)象
@property(retain, nonatomic) DTBizMessage *lastMessage;
進(jìn)一步瀏覽內(nèi)存中的這個(gè)對(duì)象,你就會(huì)發(fā)現(xiàn) ,我靠,踏破鐵下無覓處,紅包信息就在這了.其中的 attachmentsJson中存放了完整的 附件信息的json字符串
大體上如下
{
"contentType" : 901,
"@Type" : "WKIDLContentModel",
"attachments" : [
{
"@Type" : "WKIDLAttachmentModel",
"size" : 0,
"type" : 0,
"extension" : {
"amount" : "0.01",
"clusterid" : "5PnPxu8C",
"sname" : "XXX",
"size" : "1",
"congrats" : "恭喜發(fā)財(cái),大吉大利胸嘴!",
"sid" : "45049990",
"type" : "0",
"oid" : "0"
},
"isPreload" : false
}
]
}
經(jīng)過各種分析...,可以知道,紅包的contentType為901或者902.
整理下思路,
DTConversationListController --->DTConversationListDataSource *_dataSource ---> @property(retain, nonatomic) NSMutableArray<DTBizConversation * WKBizConversation *等> *conversationList ---> DTBizConversation ---> DTBizMessage ---> attachmentsJson
DTConversationListController,只要我們開啟釘釘,第一個(gè)頁面就是,所以可以認(rèn)為這個(gè)是單例,它的_dataSource也是一直存在的.
所以我們要拿到紅包的信息結(jié)構(gòu)就很簡(jiǎn)單了.
接下來,最后一個(gè)問題了.
那么我們?cè)趺粗朗裁磿r(shí)候,分析這個(gè)DTConversationListController的dataSource的conversationList的DTBizConversation的DTBizMessage的attachmentsJson呢?也就是什么時(shí)候觸發(fā)我們的自動(dòng)搶紅包動(dòng)作呢??
比較容易想到的肯定是 當(dāng)來新信息的時(shí)候.
經(jīng)過再一次的日志分析等各種分析(應(yīng)該是一個(gè)比較漫長(zhǎng),繁瑣的過程),你可以得到 每當(dāng)有新消息來的收,DTConversationListDataSource的
- (void)controller:(id)arg1 didChangeObject:(id)arg2 atIndex:(unsigned long long)arg3 forChangeType:(long long)arg4 newIndex:(unsigned long long)arg5;
方法會(huì)被調(diào)用,經(jīng)過日志輸出,我們可以得到,其第二個(gè)參數(shù)就是要插入到datasouce中的WKBizConversation/DTBizConversation
所以,只要我們hook這個(gè)方法,就可以在這觸發(fā)我們的搶紅包動(dòng)作了.
核心源碼
@implementation YLHongBaoViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getClass("DTConversationListDataSource") ;//[self class];
//- (void)controller:(id)arg1 didChangeObject:(id)arg2 atIndex:(unsigned long long)arg3 forChangeType:(long long)arg4 newIndex:(unsigned long long)arg5;
void (^hook_block)(id<AspectInfo> aspectinfo,id controller,id didChangeObject,unsigned long long atIndex,long long forChangeType,unsigned long long newIndex) = ^(id<AspectInfo> aspectinfo,id controller,id didChangeObject,unsigned long long atIndex,long long forChangeType,unsigned long long newIndex){
if (![YLHongBaoViewController isEnabled]) {
NSLog(@" 紅包分析 走原來的邏輯");
return;
}
NSMutableArray *attachArr = [DingTalkRedEnvelop disposeConversation:didChangeObject];
[attachArr enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
DTRedEnvelopServiceIMP *imp = [objc_getClass("DTRedEnvelopServiceFactory") defaultServiceIMP];
long long sid = [obj[@"sid"] longLongValue];
NSString *cluseId = obj[@"clusterid"];
NSLog(@"lingdaiping_sid = %lld,cluseid = %@",sid,cluseId);
if (cluseId.length > 0){
[imp pickRedEnvelopCluster:sid clusterId:cluseId successBlock:nil failureBlock:nil];
}
}];
};
aspect_add(class, @selector(controller:didChangeObject:atIndex:forChangeType:newIndex:), AspectPositionAfter, hook_block, nil);
});
}
+ (NSMutableArray *)disposeConversation:(WKBizConversation *)converdation {
NSLog(@"disposeConversation_sation = %@", converdation);
//WKBizConversation *converdation = sation;
NSMutableArray *retArr = [NSMutableArray new];
NSLog(@"disposeConversation_converdation.latestMessage = %@", converdation.latestMessage);
NSString *attachmentsJson = converdation.latestMessage.attachmentsJson;
NSLog(@"disposeConversation_attachmentsJson = %@", attachmentsJson);
if (attachmentsJson.length > 0) {
NSData* jsonData = [attachmentsJson dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"disposeConversation_dict = %@", dict);
NSNumber *contentType = dict[@"contentType"];
NSLog(@"disposeConversation_contentType = %@", contentType);
if (contentType.integerValue == 902 || contentType.integerValue == 901) {//紅包
NSMutableDictionary *retDict = [NSMutableDictionary new];
retDict[@"contentType"] = contentType;
NSArray *arr = dict[@"attachments"];
NSLog(@"disposeConversation_arr = %@", arr);
if (arr.count > 0) {
[arr enumerateObjectsUsingBlock:^(NSDictionary *attachmentDict, NSUInteger idx, BOOL * _Nonnull stop) {
NSDictionary *extension = attachmentDict[@"extension"];
NSLog(@"disposeConversation_extension = %@", extension);
retDict[@"clusterid"] = extension[@"clusterid"];
retDict[@"sid"] = extension[@"sid"];
NSLog(@"disposeConversation_retDict = %@", retDict);
[retArr addObject:retDict];
}];
}
}
}
return retArr;
}
備注
本文的源碼放在本文的源碼放在我的gitHub上 DingTalkNoJailTweak
有關(guān)源碼怎么使用,請(qǐng)參考源碼的Redeme.此源碼可以用在越獄環(huán)境中,也可以使用在非越獄環(huán)境中.
源碼說明
參見github上的源碼說明 https://github.com/yohunl/DingTalkNoJailTweak