花费了很多天的原创文章,转载请注明出处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 [email protected]:KJCracks/Clutch.git
cd Clutch
Build
xcodebuild -project Clutch.xcodeproj -configuration Release ARCHS="armv7 armv7s arm64" build
Install
scp /path/to/Clutch root@
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 Only dump binary files from specified bundleID
-d --dump 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 {
id _delegate;
....
}
@property(nonatomic) __weak id delegate;
- (void)didClickOpenLuckyMoneyBtn:(UIButton *)arg1;
......
@end
DTOpenLuckyMoneyEntityViewDelegate
@protocol DTOpenLuckyMoneyEntityViewDelegate
- (void)didClickViewMore:(DTOpenLuckyMoneyEntityView *)arg1;
- (void)didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
@end
DTOpenLuckyMoneyView的定义(截取我们需要的关键部分)
@interface DTOpenLuckyMoneyView : UIView
//通过此方法,构造出来视图
+ (void)showLuckyMoneyWithPickingStatus:(DTBizRedEnvelopClusterPickingStatus *)arg1 withController:(DTMessageOTOViewController *)arg2 delegate:(id )arg3;
- (void)didClickOpenLuckyMoney:(DTOpenLuckyMoneyEntityView *)arg1;
@end
通过上面的两个类和一个协议的方法定义,我们很容易的就可以得出它们之间的关系大概是如下这样的
当用户点击了拆红包按钮时
DTOpenLuckyMoneyEntityView中的button的响应事件
- (void)didClickOpenLuckyMoneyBtn:(id)arg1;
其中 先获取id _delegate; (取值是 DTOpenLuckyMoneyView (@interface DTOpenLuckyMoneyView : UIView ))
转给其方法 - (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输出,复制出来,用于分析.
这里,我将某一次的日志部分放出来,让你对这个日志有个概念
: -[DTMessageBubbleTapHandler messageBubbleViewCell:didTappedWithGestureRecognizer:] #81 [INFO] tap msg mid = 12685858885 , msgType = 902
[m +[ defaultServiceIMP]
[m -[ initWithDbConnection:]
[m =
[m +[ createServiceIMPWithPersistence: network:]
[m -[ init]
[m =
[m -[ setPersistenceIMP:]
[m -[ setNetworkIMP:]
[m -[ sendInitAlipay]
[m -[ getBindAlipaySuccessBlock:(null) failureBlock:(null)]
[m -[ networkIMP]
[m = 0x
[m -[ getBindAlipaySuccessBlock:<__NSStackBlock__: 0x16fdb5310> failureBlock:<__NSStackBlock__: 0x16fdb52e8>]
[m =
[m =
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:239[m [0;30;46mDEBUG:[m -[ checkRedEnvelopClusterPickingStatus:88160518 clusterId:5PnAJfRG successBlock:<__NSStackBlock__: 0x16fdb5638> failureBlock:<__NSStackBlock__: 0x16fdb5608>]
: -[LWPTransactionService enqueue:] #154 [Info] enqueue uri=/r/Adaptor/DingPayI/getBindAlipay [mid:1df66b00]
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:211[m [0;30;46mDEBUG:[m -[ networkIMP]
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:211[m [0;30;46mDEBUG:[m = 0x
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:265[m [0;30;46mDEBUG:[m -[ checkRedEnvelopClusterPickingStatus:88160518 clusterId:5PnAJfRG successBlock:<__NSStackBlock__: 0x16fdb5518> failureBlock:<__NSStackBlock__: 0x16fdb54e0>]
: -[LWPMessenger lwpConnection:willSendMessage:isFirstMessage:] #1075 [Info] willSend [0][[id:85b80100(addr:0x14f2461c0, idx:0) - Master]]: 1df66b00 0
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:172[m [0;30;46mDEBUG:[m +[ _serviceKey__]
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:172[m [0;30;46mDEBUG:[m = Adaptor/RedEnvelopPickIService
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:171[m [0;30;46mDEBUG:[m +[ _appname__]
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:171[m [0;30;46mDEBUG:[m = (null)
: -[LWPTransactionService enqueue:] #154 [Info] enqueue uri=/r/Adaptor/RedEnvelopPickI/checkRedEnvelopClusterPickingStatus [mid:7e426c00]
: -[LWPMessenger lwpConnection:willSendMessage:isFirstMessage:] #1075 [Info] willSend [0][[id:85b80100(addr:0x14f2461c0, idx:0) - Master]]: 7e426c00 0
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:206[m [0;30;46mDEBUG:[m -[ setBindAlipayAccount:yoh***@163.com]
: -[LWPTransactionService destoryV2:withError:withResponse:] #258 [Info] destory: mid:1df66b00 200
: -[LWPTransactionService destoryV2:withError:withResponse:] #258 [Info] destory: mid:7e426c00 200
: __98-[DTRedEnvelopServiceIMP checkRedEnvelopClusterPickingStatus:clusterId:successBlock:failureBlock:]_block_invoke #280 [INFO] clusterId=(null), flowCount=0 pickMoney=0 pickstatus=0
: WithPickingStatus = ,className = DTBizRedEnvelopClusterPickingStatus
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:109[m [0;30;46mDEBUG:[m +[ showLuckyMoneyWithPickingStatus: withController: delegate:]
: [1;36m[DingTalkTweak] [m[0;36mTweak.xm:116[m [0;30;46mDEBUG:[m +[ viewWithDelegate:]
当然了,这个分析日志的过程,你可能会持续非常多的次数,因为在分析的过程中,你可能会发现另外的某个对象可能是你要的分析,你就要再修改tweak.xm文件中的hook,将这个对象的方法全hook住,反反复复
这个时候,你应该能够得到如下的关键信息
defaultServiceIMP]
DTRedEnvelopServiceFactory通过方法defaultServiceIMP构造了一个DTRedEnvelopServiceIMP对象,然后通过这个DTRedEnvelopServiceIMP对象的pickRedEnvelopCluster: clusterId: successBlock: failureBlock方法去拆红包
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 *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
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,id controller,id didChangeObject,unsigned long long atIndex,long long forChangeType,unsigned long long newIndex) = ^(id 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