钉钉红包插件逆向开发实践

实现抢红包的基本流程

  1. 收到红包消息 (MessageCenter)
  2. 列表上出现一个红包组件( RedPocketButton )
  3. 点击进入红包页面
  4. 点击”抢红包“按钮
  5. loading
  6. 给服务器发一个请求,服务器告诉我抢到了多少钱,以及哪些人抢到了红白以及份额

##实现自动抢红包插件需要知道哪些信息

  • 如何自动拆红包?代码里面拆红包的逻辑再哪里?是否能直接调用来拆红包?如果不能需要准备哪些前置条件?
  • 我们什么时候触发抢红包这个动作?

开始分析

点击红包后,进入抢红包详情页, 其视图结构是:
UIWindow
DTOpenLuckyMoneyView
----DTOpenLuckyMoneyEntityView

查看头文件信息(Flex/class-dump)

@interface DTOpenLuckyMoneyEntityView:UIView{
     id <DTOpenLuckyMoneyEntityViewDelegate> *delegate;
     DTBizRedEnvelopClusterPickingStatus *pickingStatus;
     UIButton *_openLuckyMoneyButton;
}

- didClickOpenLuckyMoneyBtn:(id)
- openLuckyMoney:(CGFloat) stopDuration:(CGFloat) completionBlock:id
- performLoading
- stopLoading

@end

@interface DTOpenLuckyMoneyView:UIView{
     id<DTOpenLuckyMoneyViewDelegte> *delegate
     DTBizRedEnvelopClusterPickingStatus *statusModel
}

-(void)utOpenRedEnvelop:(id)
-(void)utSeeRedEnvelop:(id)
- (void)beginLoading
- (void)endLoading
...
- (void)didClickOpenLuckyMoney:(id)

@end

如何快速确定这个"拆"红包Button的 selector ? 会是这里的- didClickOpenLuckyMoneyBtn:(id) 吗?

在Flex中 查看UIButton的 _targetActions ,这是一个可变数组,每个item是一个UIControlTargetAction的对象。可以看到 _action = - didClickOpenLuckyMoneyBtn:(id) , _target 是一个 DTOpenLuckyMoneyEntityView:UIView 类型的对象,说明我们的猜测是对的。

同时我们发现DTOpenLuckyMoneyEntityView视图对象 的 delegate 是一个 DTOpenLuckyMoneyView 类型对象,也就是父 View 作为 delegate。

从目前来看,DTOpenLuckyMoneyView 有较大可能 负责拆 红包的逻辑,看下- (void)didClickOpenLuckyMoney: 方法的汇编逻辑:

大概逻辑是:

- (void)didClickOpenLuckyMoney:(id){
     [self  statusModel];
     [self utOpenRedEnvelop:]
     [self beginLoading];
     id factory = [DTRedEnvelopServiceFactory defaultServiceIMP];
     [[self redEnvelopCluster] sender] ->x25->x2
     [self clusterId] -> x28->x3
     [factory pickRedEnvelopCluster:x2 clusterId:x3 successBlock: x4, failureBlock : x5 ] //
}

看到这里,这里的pickRedEnvelopCluster:clusterId: successBlock:failureBlock:这个方法极有可能就是拆红包的api ,通过搜索 pickRedEnvelopCluster:clusterId: successBlock:failureBlock: 这个方法,不难找出哪些类有这个方法,从而定位到这是哪个类的方法。

经过搜索,这里的factory 就是 一个DTRedEnvelopServiceIMP对象了。我们看下方法的原型:

- (void)pickRedEnvelopCluster:(long long)arg1 clusterId:(id)arg2 successBlock:(CDUnknownBlockType)arg3 failureBlock:(CDUnknownBlockType)arg4;
  • cluster: sender 从名字上看可能是发送红包的用户id 。我发了一个红包,通过查看我的个人信息用户(比如信息展示页面),查询到 我的 uid 与这里的 sender 一致。
  • clusterId: 群相关 id, 形如 5PpGVx79
  • successBlock:
  • failureBlock:

同时我们发现,DTBizRedEnvelopClusterPickingStatus->DTBizRedEnvelopCluster 这里保存有红包的基本信息,我们可以使用这里的数据,call下这个api,测试看能否拆开红包。比如发了一红包后 ,sender = 79903973 ,clusterId = @“5PpKkC3K”

我们可以使用cycript/Flex call 下这个api,传入红包数据看下,这个api 是否能拆开红包。如下:

[[DTRedEnvelopServiceFactory defaultServiceIMP] pickRedEnvelopCluster:79903973 clusterId:@"5PpKkC3K" successBlock:nil failurelock:nil]

发现红包被领取了。因此确认pickRedEnvelopCluster:clusterId: successBlock:failureBlock: 就是拆红包的方法。

接下来

  • 什么时机拆红包?
  • 拆红包的参数数据 sender, clusterId 从哪里来?

但是如果没有拆红白的视图,我们就没法使用 DTBizRedEnvelopClusterPickingStatus->DTBizRedEnvelopCluster 里面的信息? 有没有可能在其它地方获得 发红包用户id群id 这两个信息呢?

可以看到,每当收到消息时,在首页列表页上都能跟新到最新的消息,因此首页上一定有接收红包信息相关的线索。如果这里也有我们需要的红包数据,问题就解决了。

会话列表页是DTConversationListController

@interface DTConversationListController:DTViewController{
     DTConversationListDataSource *dataSource;
     DTTableView *tableView; // delegate/dataSource 是 DTConversationListController
       ...
}

- observeValueForKeyPath(id) ofObject:(id) change(id) context(void *)
- onRecvMsg:(id)
- onXXX

@end

@interface DTConversationListDataSource: NSObject{
     LWFetchedResultsController *fetchController; // 其delegate 就是 DTConversationListDataSource 对象
     DTTableView *tableView; // 数据更新后,在这里直接update tableView
}

-  (void)asyncLoadAllLocalConversations;
- (void)loadAllConversations;

-  (void)controller:(id) didChangeObject:(id) atIndex:(NSInteger) forChangeType:(NSInteger) newIndex:(NSUinteger) // 应该是 LWFetchedResultsController delegate 方法

@end

当收到一条消息时,DTConversationListDataSource 哪个方法会收到消息? 是“- (void)controller:(id) didChangeObject:(id) atIndex:(NSInteger) forChangeType:(NSInteger) newIndex:(NSUinteger)” 这个方法吗? 他的参数里面都有哪些信息?

LLDB + debugserver 断点调试如下:

可以看到,收到(红包)消息时,切实可以出发断点,查看消息内容,确实是我们想要的消息数据:

通过各种测试,我们发现:

//单人红包

{
      "contentType" : 902,
      "attachments" : [
        {
          "type" : 902,
          "extension" : {
            "amount" : "1.00",
            "clusterid" : "3ga8WJ4X",
            "size" : "1",
            "congrats" : "恭喜发财,大吉大利!",
            "sid" : "14524910",
            ...
          }
        }
      ]
    }

//群红包
{
      "contentType" : 901,
      "attachments" : [
        {
          "type" : 901,
          "extension" : {
            "amount" : "1.00",
            "clusterid" : "66dAJ8IT",
            "size" : "3",
            "congrats" : "恭喜发财,大吉大利!",
            "sid" : "14524910",
               ...
          }
        }
      ]
    }

//正值元旦季节,元旦红包数据格式如下
{
      "contentType" : 905,
      "attachments" : [
        {
          "extension" : {
            "amount" : "0.10",
            "clusterid" : "FPGjJ5No",
            "p_name" : "元旦红包",
            "sid" : "16201043",
            "size" : "1",
            "congrats" : "十分祝福!",
            "sname" : “xxx"
              ...
          },
          "isPreload" : false
        }
      ]
    }

可以看到 901 是群红包,902 是个人红包, 905 是元旦红包。

Theos 工程

总结流程下:

  • hook -[DTConversationListDataSource controller:didChangeObject:atIndex:forChangeType:newIndex:]
  • 收到消息是得到红包数据 WKBizConversation->latestMessage->attachmentsJson
  • 从attachmentsJson 中解析出抢红包需要的参数sender,clusterId
  • 调用[[DTRedEnvelopServiceFactory defaultServiceIMP] pickRedEnvelopCluster:sender clusterId:clusterId successBlock:nil failureBlock:nil]; 完成自动抢红包动作

其中 bundleid = com.laiwang.DingTalk

具体代码看 github

1 个赞

赞一下楼主,说一下我的hook过程,我使用opendev开发的dylib,然后动态注入到load commands里,然后重新签名。
看了下楼主的代码,和楼主有些差异点:

  • 我是在收到消息,进行消息model转换的地方加了hook代码,这点感觉楼主的更智能,从数据源变更的时候触发抢红包。

  • dd抢红包过程,主要发起了四种请求:更新已读状态、视图状态请求;检测支付宝绑定请求;检测红包状态请求(点击红包的时候);拆红包的请求。个人觉得还是按照dd的流程来处理好一些,这样可能能避免一些反作弊规则。

3 个赞

谢谢分享,比我分析的仔细~

钉钉做了签名判断,这个现在还能用吗?

1 个赞