APP逆向分析之釘釘搶紅包插件的實現(xiàn)-iOS篇

花費了很多天的原創(chuàng)文章,轉(zhuǎn)載請注明出處https://yohunl.com/ding-ding-qiang-hong-bao-cha-jian-iospian/ ,謝謝!

網(wǎng)絡上關于微信紅包的分析文章已經(jīng)非常多了,基本上照著做就可以弄個微信搶紅包插件出來,不過,隨著阿里巴巴的釘釘在企業(yè)中的流行,很多企業(yè)現(xiàn)在都采用釘釘來辦公了,順帶著,也就使用釘釘來發(fā)紅包了,學習了那么多逆向的理論后,需要拿一個東西來練練手,剛好,釘釘就符合這個要求,于是,便有了下面的這篇文章.套用騰訊的何兆林在文章 移動App入侵與逆向破解技術-iOS篇說的一段話
"破解有時候很耗時,和程序開發(fā)正好相反,它耗時不是耗在寫代碼上,而是耗在尋找注入點和逆向工程上,有可能你花了3天時間去找程序的破綻,但是最終的破解代碼可能就2行,不到一分鐘就搞定了;但是你也需要做好面對失敗的準備,如果路選錯了,有可能你這3天完全是在浪費腦細胞"

本文的源碼放在我的gitHub上 DingTalkNoJailTweak ,,其ReadMe文件說明了其中相關的代碼是起什么作用的.

當然了,這篇文件會涉及比較多的逆向的東西,這些理論等前序知識,可以參考網(wǎng)上的,網(wǎng)上的資料比較多了,比如TheOS的開發(fā)環(huán)境的搭建,Hopper逆向分析軟件的使用,ida Pro等,移動App入侵與逆向破解技術-iOS篇上就有很不錯的前序理論知識的介紹.
好了,閑話不多說,直接進入正題.

相關工具和環(huán)境

一臺越獄的手機,雖然不越獄的手機也可以用來分析,但是,那樣將會大大的減慢整個分析過程,因為非越獄的只能通過注入后打log日志來分析,耗時耗力,所以,越獄手機是必備

越獄的手機上安裝了

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>
當然你可以使用同步助手,PP助手,iFunBox,iTools Pro來安裝到手機的/usr/bin下
如果不是用SSH從電腦上登陸手機的命令行的話,你可以cydia下載MTerminal,然后使用su切換用戶到root,再使用命令 chmod +x clutch來提升權限

MTerminal

cydia中下載,可以方便的在你的手機上使用終端命令行.

一臺MAC電腦安裝了

class-dump

class-dump,用來dump出二進制的頭文件.

Theos

可以參考我的另外一篇文章來安裝 iOS 越獄的Tweak開發(fā)

Hopper Disassembler v3 或者ida Pro

用來進行匯編分析的

xcode

insert_dylib

iTools Pro

iTools
方便的用來拷貝.瀏覽手機上的所有文件

砸殼

砸殼,是為了能夠使用class-dump來導出所有的頭文件,有了頭文件,我們分析起來才是有可能的.
ssh連上你的越獄手機/或者像我一樣直接在手機上的Terminal中操作,調(diào)用clutch來進行砸殼

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就是 用來砸殼的,需要一個參數(shù)是要被砸殼的應用的bundleid,獲取釘釘?shù)腷undleid有很多方法,你可以直接使用 clutch -i 輸出手機上所有已經(jīng)安裝的應用,找到bundleid.釘釘?shù)腷undleid是com.laiwang.DingTalk
開始砸殼



稍等一會,砸殼完畢,會輸出砸完殼后的存放路徑

使用iTool Pro/PP助手等工具,將砸完殼的釘釘拷貝到電腦上

導出頭文件

在電腦上,將砸殼后的文件中讀取出頭文件, DingTalk是從砸殼后的ipa包中提取出來的釘釘?shù)目蓤?zhí)行文件,這個文件比較好找,一般都是沒有后綴的,大小最大的那個.

class-dump -H DingTalk -o DingTalkHeaders

class-dump的簡單使用方式
最簡單的使用方式
class-dump -H DDMMerchant -o yicommonHeaders
class-dump -H Payload/WeChat.app/WeChat -o wechatHeaders

還可以加上 -S -s來對生成的方法,類都進行排序
class-dump -S -s -H Payload/WeChat.app/WeChat -o wechatHeaders
一定要寫 -H,不然,都是在命令行輸出的,不會將內(nèi)容輸入到文件夾下!!

導出頭文件后,新建一個xcode工程,將所有的頭文件都導出到工程中,為什么做這一步呢,因為,Xcode的搜索能力還是不錯的,方便我們?nèi)ゲ榭催@些頭文件.當然,由于一次性導入xcode工程的文件量比較大,在導入的過程中,xcode可能會假死,稍微耐心一點.

開始分析

在這里,強烈的推薦Flipboard的FLEX,這個簡直就是iOS分析界的神器啊.以前還要用命令才能一步步的將界面所對應的視圖,以及相應的ViewController分析出來,現(xiàn)在有了它,這個過程可以大大加速了.

如果你安裝了FlexInjected,打開設置那里FlexInjected中釘釘?shù)拈_關,這樣,當釘釘啟動的時候,就加載了Flex了,加載了Flex后,會在釘釘中出現(xiàn)Flex的界面,點擊界面可以進行相應的操作.
首先,第一步,我們先要找到搶紅包的視圖所對應的對象以及相應的VC,這個事情利用Flex還是很簡單的.

設置中打開 FlexInjected中的釘釘,這樣,釘釘啟動的時候,就會加載FLex.


打開釘釘,就會出現(xiàn)Flex的菜單了,Flex的基本操作,可以參考FLex官網(wǎng)的.


通過Flex,我們很容易的得到下面的結(jié)果
1 聊天頁面的紅包視圖的View是DTMessageBubbleRedEnvelopView



2 點擊后,搶紅包的頁面的視圖是

DTOpenLuckyMoneyView
----DTOpenLuckyMoneyEntityView


3 所有的聊天的會話的控制器是DTConversationListViewController.

我們已經(jīng)知道了搶紅包的關鍵視圖是DTOpenLuckyMoneyEntityView 和DTOpenLuckyMoneyView.打開我們dump出來的所有的頭文件,查看DTOpenLuckyMoneyEntityView頭文件定義
DTOpenLuckyMoneyEntityView的定義(截取我們需要的關鍵部分)

@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的定義(截取我們需要的關鍵部分)

@interface DTOpenLuckyMoneyView : UIView <DTOpenLuckyMoneyEntityViewDelegate>
//通過此方法,構造出來視圖
+ (void)showLuckyMoneyWithPickingStatus:(DTBizRedEnvelopClusterPickingStatus *)arg1 withController:(DTMessageOTOViewController *)arg2 delegate:(id <DTOpenLuckyMoneyViewDelegate>)arg3;
- (void)didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
@end

通過上面的兩個類和一個協(xié)議的方法定義,我們很容易的就可以得出它們之間的關系大概是如下這樣的

當用戶點擊了拆紅包按鈕時

DTOpenLuckyMoneyEntityView中的button的響應事件 
- (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
}

這只是大概的流程,我們可以進一步細化這個流程,這個時候就要用到Hopper Disassembler或者iDA Pro了,用Hopper打開釘釘?shù)目蓤?zhí)行文件,等待分析完畢,打開DTOpenLuckyMoneyView的didClickViewMore實現(xiàn),(這里我只截取了部分)


從這個方法的匯編代碼中,我們大體上可以得到如下的信息:

DTOpenLuckyMoneyView didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
   [self statusModel]; //DTBizRedEnvelopClusterPickingStatus *statusModel,其中有一個屬性是DTBizRedEnvelopCluster,包含了紅紅包的相關信息,看上面的截圖
   [self utOpenRedEnvelop:statusModel]
   [self beginLoading]//其中會調(diào)用DTOpenLuckyMoneyEntityView的loading方法等
   [DTRedEnvelopServiceFactory defaultServiceIMP]獲取一個 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這個類,還有pickRedEnvelopCluster方法,在頭文件中搜索pickRedEnvelopCluster,可以得到



我們又得到了幾個關聯(lián)的類,尤其是其中的DTRedEnvelopService.

到這里,你可能還是很迷惑,下步該做什么?

不要緊,TheOS給我們提供了一個logify.pl腳本(theos/bin/logify),這個腳本可以將一個類中的所有方法都加上日志.
大概的使用方式就是

$THEOS/bin/logify.pl ./DTMessageMTMViewController.h  在終端顯示結(jié)果
$THEOS/bin/logify.pl ./DTMessageMTMViewController.h > /out/to/DTMessageMTMViewController.xm
 

我們使用Theos建立一個tweak,這個tweak的作用就是輸出日志,有關theos的安裝和使用,可以參考我的另外一篇博客iOS 越獄的Tweak開發(fā),我們將我們覺得可疑的類都加上日志,編譯一個tweak,安裝到手機上.
你的這個tweak.xm文件中的內(nèi)容應該是大致如下

%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ā)一個紅包,然后你點擊拆紅包,拆掉紅包,將整個的Log輸出,復制出來,用于分析.
這里,我將某一次的日志部分放出來,讓你對這個日志有個概念

<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>]


當然了,這個分析日志的過程,你可能會持續(xù)非常多的次數(shù),因為在分析的過程中,你可能會發(fā)現(xiàn)另外的某個對象可能是你要的分析,你就要再修改tweak.xm文件中的hook,將這個對象的方法全hook住,反反復復

這個時候,你應該能夠得到如下的關鍵信息

<DTRedEnvelopServiceFactory: 0x103303bd8> defaultServiceIMP]

DTRedEnvelopServiceFactory通過方法defaultServiceIMP構造了一個DTRedEnvelopServiceIMP對象,然后通過這個DTRedEnvelopServiceIMP對象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法去拆紅包

<DTRedEnvelopServiceFactory: 0x103303bd8> defaultServiceIMP]
DTRedEnvelopServiceIMP - (void)pickRedEnvelopCluster:(long long)arg1 clusterId:(id)arg2 successBlock:(CDUnknownBlockType)arg3 failureBlock:(CDUnknownBlockType)arg4;

這個方法就是最后的拆紅包功能的方法,到此
拆紅包這個就很簡答了,由于[DTRedEnvelopServiceFactory defaultServiceIMP]是一個類方法,所以不需要我們構造的,那么現(xiàn)在的問題是,在[DTRedEnvelopServiceFactory defaultServiceIMP]周后,有沒有調(diào)用其它的方法來配置這個構造出來的DTRedEnvelopServiceIMP對象呢?
我們可以繼續(xù)根據(jù)輸出的日志來分析....

這里,我直接告訴你分析的結(jié)果
[DTRedEnvelopServiceFactory defaultServiceIMP]方法中,會初始化一個全局的DTRedEnvelopServiceIMP對象.并且配置好它,例如綁定支付信息等等 (你可以從匯編中大概看一下).返回給的DTRedEnvelopServiceIMP對象就是一個完全可用的對象了,只要調(diào)用這個對象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法,就可以完成拆紅包這個動作了.

那么,自動拆紅包這個,就變?yōu)槟阍趺茨玫竭@個方法所需的4個參數(shù)了,后兩個block,一看就知道可以傳nil的,因為我們對成功失敗的回調(diào)不感興趣,問題就變?yōu)樵趺茨玫角皟蓚€參數(shù)了.
第一個參數(shù)是一個 long long類型,第二個是一個 NSString.

FLex有一個非常好用的功能,是其中帶有的Heap Objects,這個可以用來查看當前內(nèi)存中有哪些方法.結(jié)合我們上面的日志分析,會發(fā)現(xiàn)
DTOpenLuckyMoneyEntityView 對象中的方法 - (DTBizRedEnvelopCluster *)currentCluster;
其中包含了我們需要的紅包的相關信息


接下來,我們讓別人再發(fā)一個紅包,然后,點擊出現(xiàn)拆紅包視圖,這個時候,利用Flex的Heap Objects查內(nèi)存中的DTBizRedEnvelopCluster方法,從中看到@property(copy, nonatomic) NSString *clusterId;和@property(nonatomic) long long sender,記下來

再搜尋 DTRedEnvelopServiceIMP對象,Flex可以直接調(diào)用搜尋出來的某個對象,在Flex中直接調(diào)用方法pickRedEnvelopCluster,傳遞給其,注意,這里在Flex中第二個參數(shù)是字符串,使用""而不是@"".


點擊右上角的call,調(diào)用這個方法,不出意外,你會發(fā)現(xiàn),紅包已經(jīng)被拆了,到此,驗證了,的確是這個方法,而且也知道了參數(shù)怎么拿到的.

OK,問題又來了,如果沒有出現(xiàn)拆紅包視圖,那么就沒有這個DTBizRedEnvelopCluster類呀,那怎么獲取其中的兩個參數(shù)出來呢?

這的確是個問題,你要再回到匯編和頭文件中,去分析了....

每當來一個新消息,消息的列表頁面就已經(jīng)收到了,這說明在這個頁面的時候,應該已經(jīng)獲取到了搶紅包所需要的相關信息了.我們接下來找到他們之間的聯(lián)系

我們平時看到的聊天的集合信息頁是 DTConversationListController,瀏覽頭文件,看到DTConversationListDataSource *_dataSource;
進入DTConversationListDataSource,看到其中有

@property(retain, nonatomic) NSMutableArray<DTBizConversation *  WKBizConversation *等> *conversationList

從Heap Object進去,查看這個conversationList對象中的內(nèi)容,可以看到其中存放的大概是DTBizConversation或者是WKBizConversation,繼續(xù)瀏覽
我們看到DTBizConversation中,有一個對象
@property(retain, nonatomic) DTBizMessage *lastMessage;
進一步瀏覽內(nèi)存中的這個對象,你就會發(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ā)財,大吉大利!",
        "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,只要我們開啟釘釘,第一個頁面就是,所以可以認為這個是單例,它的_dataSource也是一直存在的.

所以我們要拿到紅包的信息結(jié)構就很簡單了.

接下來,最后一個問題了.
那么我們怎么知道什么時候,分析這個DTConversationListController的dataSource的conversationList的DTBizConversation的DTBizMessage的attachmentsJson呢?也就是什么時候觸發(fā)我們的自動搶紅包動作呢??

比較容易想到的肯定是 當來新信息的時候.
經(jīng)過再一次的日志分析等各種分析(應該是一個比較漫長,繁瑣的過程),你可以得到 每當有新消息來的收,DTConversationListDataSource的

- (void)controller:(id)arg1 didChangeObject:(id)arg2 atIndex:(unsigned long long)arg3 forChangeType:(long long)arg4 newIndex:(unsigned long long)arg5;

方法會被調(diào)用,經(jīng)過日志輸出,我們可以得到,其第二個參數(shù)就是要插入到datasouce中的WKBizConversation/DTBizConversation

所以,只要我們hook這個方法,就可以在這觸發(fā)我們的搶紅包動作了.

核心源碼

@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
有關源碼怎么使用,請參考源碼的Redeme.此源碼可以用在越獄環(huán)境中,也可以使用在非越獄環(huán)境中.

源碼說明

參見github上的源碼說明 https://github.com/yohunl/DingTalkNoJailTweak

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容