网络上关于微信红包的分析文章已经非常多了,基本上照着做就可以弄个微信抢红包插件出来,不过,随着阿里巴巴的钉钉在企业中的流行,很多企业现在都采用钉钉来办公了,顺带着,也就使用钉钉来发红包了,学习了那么多逆向的理论后,需要拿一个东西来练练手,刚好,钉钉就符合这个要求,于是,便有了下面的这篇文章.套用腾讯的何兆林在文章 移动App入侵与逆向破解技术-iOS篇 说的一段话
"破解有时候很耗时,和程序开发正好相反,它耗时不是耗在写代码上,而是耗在寻找注入点和逆向工程上,有可能你花了3天时间去找程序的破绽,但是最终的破解代码可能就2行,不到一分钟就搞定了;但是你也需要做好面对失败的准备,如果路选错了,有可能你这3天完全是在浪费脑细胞"
本文的源码放在我的gitHub上 DingTalkNoJailTweak
当然了,这篇文件会涉及比较多的逆向的东西,这些理论等前序知识,可以参考网上的,网上的资料比较多了,比如TheOS的开发环境的搭建,Hopper逆向分析软件的使用,ida Pro等, 移动App入侵与逆向破解技术-iOS篇 上就有很不错的前序理论知识的介绍.
好了,闲话不多说,直接进入正题.
一台越狱的手机,虽然不越狱的手机也可以用来分析,但是,那样将会大大的减慢整个分析过程,因为非越狱的只能通过注入后打log日志来分析,耗时耗力,所以,越狱手机是必备 越狱的手机上安装了 cycript
FlexInjected
clutch
Terminal
一台MAC电脑 安装了 class-dump Theos
Hopper Disassembler v3 或者ida Pro
xcode
insert_dylib
iTools Pro
砸壳,是为了能够使用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可能会假死,稍微耐心一点.
在这里,强烈的推荐Flex,这个简直就是iOS分析界的神器啊.以前还要用命令才能一步步的将界面所对应的视图,以及相应的ViewController分析出来,现在有了它,这个过程可以大大加速了.
如果你安装了FlexInjected,打开设置那里FlexInjected中钉钉的开关,这样,当钉钉启动的时候,就加载了Flex了,加载了Flex后,会在钉钉中出现Flex的界面,点击界面可以进行相应的操作. 首先,第一步,我们先要找到抢红包的视图所对应的对象以及相应的VC,这个事情利用Flex还是很简单的.
设置中打开 FlexInjected中的钉钉,这样,钉钉启动的时候,就会加载FLex.
打开钉钉,就会出现Flex的菜单了,Flex的基本操作,可以参考FLex官网的.
通过Flex,我们很容易的得到下面的结果 1 聊天页面的红包视图的View是DTMessageBubbleRedEnvelopView
2 点击后,抢红包的页面的视图是
DTOpenLuckyMoneyView
----DTOpenLuckyMoneyEntityView3 所有的聊天的会话VC是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 didClickViewMore: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" : "林晓然", "size" : "1", "congrats" : "恭喜发财,大吉大利!", "sid" : "45049990", "type" : "0", "oid" : "0" }, "isPreload" : false } ] }
经过各种分析...,可以知道,红包的contentType为901或者902.
整理下思路, DTConversationListController --->DTConversationListDataSource *_dataSource ---> @property(retain, nonatomic) NSMutableArray
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.此源码可以用在越狱环境中,也可以使用在非越狱环境中.