yohunl's iOS blog

时间顺流而下,生活逆水行舟

用来记录平时的一些心得!


APP逆向分析之钉钉抢红包插件的实现-iOS篇

花费了很多天的原创文章,转载请注明出处https://yohunl.com/ding-ding-qiang-hong-bao-cha-jian-iospian/ ,谢谢!

网络上关于微信红包的分析文章已经非常多了,基本上照着做就可以弄个微信抢红包插件出来,不过,随着阿里巴巴的钉钉在企业中的流行,很多企业现在都采用钉钉来办公了,顺带着,也就使用钉钉来发红包了,学习了那么多逆向的理论后,需要拿一个东西来练练手,刚好,钉钉就符合这个要求,于是,便有了下面的这篇文章.套用腾讯的何兆林在文章 移动App入侵与逆向破解技术-iOS篇说的一段话
"破解有时候很耗时,和程序开发正好相反,它耗时不是耗在写代码上,而是耗在寻找注入点和逆向工程上,有可能你花了3天时间去找程序的破绽,但是最终的破解代码可能就2行,不到一分钟就搞定了;但是你也需要做好面对失败的准备,如果路选错了,有可能你这3天完全是在浪费脑细胞"

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

当然了,这篇文件会涉及比较多的逆向的东西,这些理论等前序知识,可以参考网上的,网上的资料比较多了,比如TheOS的开发环境的搭建,Hopper逆向分析软件的使用,ida Pro等,移动App入侵与逆向破解技术-iOS篇上就有很不错的前序理论知识的介绍.
好了,闲话不多说,直接进入正题.

相关工具和环境

一台越狱的手机,虽然不越狱的手机也可以用来分析,但是,那样将会大大的减慢整个分析过程,因为非越狱的只能通过注入后打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@:/usr/bin/
ssh root@
当然你可以使用同步助手,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开发

Hopper Disassembler v3 或者ida Pro

用来进行汇编分析的

xcode

insert_dylib

iTools Pro

iTools 方便的用来拷贝.浏览手机上的所有文件

砸壳

砸壳,是为了能够使用class-dump来导出所有的头文件,有了头文件,我们分析起来才是有可能的. ssh连上你的越狱手机/或者像我一样直接在手机上的Terminal中操作,调用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就是 用来砸壳的,需要一个参数是要被砸壳的应用的bundleid,获取钉钉的bundleid有很多方法,你可以直接使用 clutch -i 输出手机上所有已经安装的应用,找到bundleid.钉钉的bundleid是com.laiwang.DingTalk 开始砸壳 稍等一会,砸壳完毕,会输出砸完壳后的存放路径

使用iTool Pro/PP助手等工具,将砸完壳的钉钉拷贝到电脑上

导出头文件

在电脑上,将砸壳后的文件中读取出头文件, DingTalk是从砸壳后的ipa包中提取出来的钉钉的可执行文件,这个文件比较好找,一般都是没有后缀的,大小最大的那个.

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,不然,都是在命令行输出的,不会将内容输入到文件夹下!!

导出头文件后,新建一个xcode工程,将所有的头文件都导出到工程中,为什么做这一步呢,因为,Xcode的搜索能力还是不错的,方便我们去查看这些头文件.当然,由于一次性导入xcode工程的文件量比较大,在导入的过程中,xcode可能会假死,稍微耐心一点.

开始分析

在这里,强烈的推荐Flipboard的FLEX,这个简直就是iOS分析界的神器啊.以前还要用命令才能一步步的将界面所对应的视图,以及相应的ViewController分析出来,现在有了它,这个过程可以大大加速了.

如果你安装了FlexInjected,打开设置那里FlexInjected中钉钉的开关,这样,当钉钉启动的时候,就加载了Flex了,加载了Flex后,会在钉钉中出现Flex的界面,点击界面可以进行相应的操作. 首先,第一步,我们先要找到抢红包的视图所对应的对象以及相应的VC,这个事情利用Flex还是很简单的.

设置中打开 FlexInjected中的钉钉,这样,钉钉启动的时候,就会加载FLex.

打开钉钉,就会出现Flex的菜单了,Flex的基本操作,可以参考FLex官网的.

通过Flex,我们很容易的得到下面的结果 1 聊天页面的红包视图的View是DTMessageBubbleRedEnvelopView
2 点击后,抢红包的页面的视图是

DTOpenLuckyMoneyView
----DTOpenLuckyMoneyEntityView

3 所有的聊天的会话的控制器是DTConversationListViewController.

我们已经知道了抢红包的关键视图是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

通过上面的两个类和一个协议的方法定义,我们很容易的就可以得出它们之间的关系大概是如下这样的

当用户点击了拆红包按钮时

DTOpenLuckyMoneyEntityView中的button的响应事件  
- (void)didClickOpenLuckyMoneyBtn:(id)arg1;
    其中  先获取id <DTOpenLuckyMoneyEntityViewDelegate> _delegate; (取值是 DTOpenLuckyMoneyView (@interface DTOpenLuckyMoneyView : UIView <DTOpenLuckyMoneyEntityViewDelegate>))
    转给其方法 - (void)didClickViewMore:(DTOpenLuckyMoneyEntityView *)arg1;
也就是  
DTOpenLuckyMoneyEntityView  
- (void)didClickOpenLuckyMoneyBtn:(id)sender {
   [self.delegate didClickOpenLuckyMoney:self];//_delegate; (取值是 DTOpenLuckyMoneyView
}

这只是大概的流程,我们可以进一步细化这个流程,这个时候就要用到Hopper Disassembler或者iDA Pro了,用Hopper打开钉钉的可执行文件,等待分析完毕,打开DTOpenLuckyMoneyView的didClickViewMore实现,(这里我只截取了部分)
从这个方法的汇编代码中,我们大体上可以得到如下的信息:

DTOpenLuckyMoneyView didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;  
   [self statusModel]; //DTBizRedEnvelopClusterPickingStatus *statusModel,其中有一个属性是DTBizRedEnvelopCluster,包含了红红包的相关信息,看上面的截图
   [self utOpenRedEnvelop:statusModel]
   [self beginLoading]//其中会调用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,可以得到 我们又得到了几个关联的类,尤其是其中的DTRedEnvelopService.

到这里,你可能还是很迷惑,下步该做什么?

不要紧,TheOS给我们提供了一个logify.pl脚本(theos/bin/logify),这个脚本可以将一个类中的所有方法都加上日志. 大概的使用方式就是

$THEOS/bin/logify.pl ./DTMessageMTMViewController.h  在终端显示结果
$THEOS/bin/logify.pl ./DTMessageMTMViewController.h > /out/to/DTMessageMTMViewController.xm

我们使用Theos建立一个tweak,这个tweak的作用就是输出日志,有关theos的安装和使用,可以参考我的另外一篇博客iOS 越狱的Tweak开发,我们将我们觉得可疑的类都加上日志,编译一个tweak,安装到手机上. 你的这个tweak.xm文件中的内容应该是大致如下

%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

接着,你在钉钉中,让别人发一个红包,然后你点击拆红包,拆掉红包,将整个的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>]

当然了,这个分析日志的过程,你可能会持续非常多的次数,因为在分析的过程中,你可能会发现另外的某个对象可能是你要的分析,你就要再修改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]是一个类方法,所以不需要我们构造的,那么现在的问题是,在[DTRedEnvelopServiceFactory defaultServiceIMP]周后,有没有调用其它的方法来配置这个构造出来的DTRedEnvelopServiceIMP对象呢?
我们可以继续根据输出的日志来分析....

这里,我直接告诉你分析的结果 [DTRedEnvelopServiceFactory defaultServiceIMP]方法中,会初始化一个全局的DTRedEnvelopServiceIMP对象.并且配置好它,例如绑定支付信息等等 (你可以从汇编中大概看一下).返回给的DTRedEnvelopServiceIMP对象就是一个完全可用的对象了,只要调用这个对象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法,就可以完成拆红包这个动作了.

那么,自动拆红包这个,就变为你怎么拿到这个方法所需的4个参数了,后两个block,一看就知道可以传nil的,因为我们对成功失败的回调不感兴趣,问题就变为怎么拿到前两个参数了. 第一个参数是一个 long long类型,第二个是一个 NSString.

FLex有一个非常好用的功能,是其中带有的Heap Objects,这个可以用来查看当前内存中有哪些方法.结合我们上面的日志分析,会发现
DTOpenLuckyMoneyEntityView 对象中的方法 - (DTBizRedEnvelopCluster *)currentCluster;
其中包含了我们需要的红包的相关信息

接下来,我们让别人再发一个红包,然后,点击出现拆红包视图,这个时候,利用Flex的Heap Objects查内存中的DTBizRedEnvelopCluster方法,从中看到@property(copy, nonatomic) NSString *clusterId;和@property(nonatomic) long long sender,记下来

再搜寻 DTRedEnvelopServiceIMP对象,Flex可以直接调用搜寻出来的某个对象,在Flex中直接调用方法pickRedEnvelopCluster,传递给其,注意,这里在Flex中第二个参数是字符串,使用""而不是@"".

点击右上角的call,调用这个方法,不出意外,你会发现,红包已经被拆了,到此,验证了,的确是这个方法,而且也知道了参数怎么拿到的.

OK,问题又来了,如果没有出现拆红包视图,那么就没有这个DTBizRedEnvelopCluster类呀,那怎么获取其中的两个参数出来呢?

这的确是个问题,你要再回到汇编和头文件中,去分析了....

每当来一个新消息,消息的列表页面就已经收到了,这说明在这个页面的时候,应该已经获取到了抢红包所需要的相关信息了.我们接下来找到他们之间的联系

我们平时看到的聊天的集合信息页是 DTConversationListController,浏览头文件,看到DTConversationListDataSource *_dataSource; 进入DTConversationListDataSource,看到其中有

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

从Heap Object进去,查看这个conversationList对象中的内容,可以看到其中存放的大概是DTBizConversation或者是WKBizConversation,继续浏览 我们看到DTBizConversation中,有一个对象 @property(retain, nonatomic) DTBizMessage *lastMessage; 进一步浏览内存中的这个对象,你就会发现 ,我靠,踏破铁下无觅处,红包信息就在这了.其中的 attachmentsJson中存放了完整的 附件信息的json字符串 大体上如下

{
  "contentType" : 901,
  "@Type" : "WKIDLContentModel",
  "attachments" : [
    {
      "@Type" : "WKIDLAttachmentModel",
      "size" : 0,
      "type" : 0,
      "extension" : {
        "amount" : "0.01",
        "clusterid" : "5PnPxu8C",
        "sname" : "XXX",
        "size" : "1",
        "congrats" : "恭喜发财,大吉大利!",
        "sid" : "45049990",
        "type" : "0",
        "oid" : "0"
      },
      "isPreload" : false
    }
  ]
}

经过各种分析...,可以知道,红包的contentType为901或者902.

整理下思路, DTConversationListController --->DTConversationListDataSource *_dataSource ---> @property(retain, nonatomic) NSMutableArray *conversationList ---> DTBizConversation ---> DTBizMessage ---> attachmentsJson

DTConversationListController,只要我们开启钉钉,第一个页面就是,所以可以认为这个是单例,它的_dataSource也是一直存在的.

所以我们要拿到红包的信息结构就很简单了.

接下来,最后一个问题了. 那么我们怎么知道什么时候,分析这个DTConversationListController的dataSource的conversationList的DTBizConversation的DTBizMessage的attachmentsJson呢?也就是什么时候触发我们的自动抢红包动作呢??

比较容易想到的肯定是 当来新信息的时候.
经过再一次的日志分析等各种分析(应该是一个比较漫长,繁琐的过程),你可以得到 每当有新消息来的收,DTConversationListDataSource的

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

方法会被调用,经过日志输出,我们可以得到,其第二个参数就是要插入到datasouce中的WKBizConversation/DTBizConversation

所以,只要我们hook这个方法,就可以在这触发我们的抢红包动作了.

核心源码

@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.此源码可以用在越狱环境中,也可以使用在非越狱环境中.

源码说明

参见github上的源码说明 https://github.com/yohunl/DingTalkNoJailTweak

更早的文章

OC代码规范的spacecommander使用

引子 大到每个公司,小到每个人,都有自己的编程习惯,好的编程习惯的保留,得到大家的认同,也就成了规范,目前大体上有几个派系的规范,首当其冲的就是Google的规范. 我们平时在Xcode中的格式规范使…

继续阅读