微信集赞/评论插件分析及开发

By xia0

微信集赞/评论插件分析及开发

开始

为什么要做这个集赞的插件呢?起因是上周去参加了某个会议,有一个集赞60领玩偶的活动,但是想到平时一条朋友圈也就几个赞,而且又不想找人点赞,领不到,很气。回去以后想着能不能写一个集赞的插件,在需要的时候直接输入想要的赞、评论数量,我发的朋友圈就能有多少赞。这样再有这样的活动岂不美哉。准备开干!

理性分析

再开始之前,先理性分析一波:如果想要集赞,这里有两种思路,一个是直接在view层去更改,但是这样得去处理界面的一些细节,一旦不注意,很容易崩溃。还有种思路是更改datasource或者说赞评论的模型。可以想到,最初一条朋友圈肯定是从服务器拿到数据并封装成对应的模型。一般来说,越改底层的数据或者说源头的数据,那么稳定性和真实性就更高。这里我的想法就是既不改view层,也不改源头层,就改封装好的模型那一层应该就很符合要求。接下来主要讲一下怎么我去实现这个需求的分析过程,因为本身功能不是很复杂,大佬随便看看就行。

准备条件

  • 一台mac
  • 一台越狱的iOS设备
  • ida/Hooper/theos
  • flex/issh/xia0LLDB
  • 其他常见逆向工具等

对于一贯喜欢上调试器分析得我,所以写了iSSHxia0LLDB两个工具,在这两个工具的辅助下整个插件用了2小时就完成了逆向分析和代码实现。

逆向分析

逆向赞和评论的数据模型

一切从界面入手,这里分析界面我一般喜欢用flex,在微信的朋友圈界面,用flex很容易发现当前界面的控制器为WCTimeLineViewController而且界面是一个UITableView

将wechat执行文件拖入Hooper(wechat文件太大,ida分析会很卡)找到UITableView的代理方法:
-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]

这里面肯定会根据模型去设置cell数据

  r2 = [r28 section];
  var_70 = r25;
  r24 = [r25 calcDataItemIndex:r2];
  r25 = [[MMServiceCenter defaultCenter] retain];
  r2 = [WCFacade class];
  var_78 = r28;
  r0 = [r25 getService:r2];
  r0 = [r0 retain];
  r24 = [[r0 getTimelineDataItemOfIndex:r24] retain];
  [r0 release];
  [r25 release];
  r19 = [[MMServiceCenter defaultCenter] retain];
  r0 = [r19 getService:[WCFacade class]];
  r0 = [r0 retain];
  r25 = [[r0 getLayerIdForDataItem:r24] retain];
  [r0 release];
  [r19 release];
  r19 = [[MMServiceCenter defaultCenter] retain];
  r0 = [r19 getService:[WCFacade class]];
  r0 = [r0 retain];
  r20 = r0;
  r0 = [r0 getShowTip:r24 layerId:r25];
  r29 = r29;
  r26 = [r0 retain];
  [r20 release];
  [r19 release];

整理下来就是

[[MMServiceCenter defaultCenter] getService:[WCFacade class]]会得到一个WCFacade对象,然后通过

[WCFacade getTimelineDataItemOfIndex:]就能得到cell的数据

看到这里,上调试器!看下都是什么数据…

将设备用数据线连接上电脑(这里我推荐用数据线的方式,wifi延时太高,影响心情),手机上打开微信

直接输入issh debug -a wechat就能挂上微信

xia0 ~ $ issh debug -a wechat
[I]:iproxy process for 2222 port alive, pid=1382
[I]:++++++++++++++++++ Nice to Work :) +++++++++++++++++++++
[I]:iOSRE dir exist
[I]:iproxy process for 1234 port alive, pid=1395
[I]:Run ps -e | grep debugserver | grep -v grep; [[ 0 == 0 ]] && (killall -9 debugserver 2> /dev/null)
[I]:/iOSRE/tools/debugserver file exist, Start debug...
[I]:Run /iOSRE/tools/debugserver 127.0.0.1:1234 -a wechat

打开另一个终端进行调试(我的lldb已经安装了xia0LLDB脚本)

xia0 ~ $ lldb
"xutil" command installed -> xutil
"choose" command installed -> choose
"xbr" command installed --> xbr -[UIView initWithFrame:]
"sbt" command installed -> sbt
// 连接到远端
(lldb) pcc

这里有两种方法:

一种是用xbr "-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]"下断点去查看;

// 对-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]方法下断点
(lldb) xbr "-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]"
(lldb) c

第二种是由于lldb支持choose命令,可以直接拿到WCFacade对象。既然如此选choose

(lldb) choose WCFacade
<__NSArrayM 0x2823d9860>(
<WCFacade: 0x139e1c030>
)

调用其getTimelineDataItemOfIndex:方法就能拿到第一条朋友圈的数据

(lldb) po [0x139e1c030 getTimelineDataItemOfIndex:0]
Class name: WCDataItem, addr: 0x13e2871d0
tid: 13121667995275047007
username: wxid_6913ohfkk7kq12
createtime: 1564224719
commentUsers: (
)
contentObj: <WCContentItem: 0x2801f5500>

输入ivars 0x13e2871d0就能拿到对象的所有属性值

(lldb) ivars 0x13e2871d0
<WCDataItem: 0x13e2871d0>:
in WCDataItem:
    cid (int): 0
    tid (NSString*): @"13121667995275047007"
    type (int): 0
    flag (int): 0
    username (NSString*): @"wxid_6913ohfkk7kq12"
    nickname (NSString*): @"xia0"
    createtime (int): 1564224719
    locationInfo (WCLocationInfo*): <WCLocationInfo: 0x2801f7800>
    likeFlag (BOOL): NO
    likeCount (int): 0
    likeUsers (NSMutableArray*): <__NSArrayM: 0x286152d90>
    commentCount (int): 0
    commentUsers (NSMutableArray*): <__NSArrayM: 0x2861539c0>
    contentObj (WCContentItem*): <WCContentItem: 0x2801f5500>
    appInfo (WCAppInfo*): <WCAppInfo: 0x287404080>
    contentDesc (NSString*): @"test"

由于属性太多,这里我就只显示了一些比较关心的数据,可以看到这就是我发的一条内容为test的朋友圈。

里面我们还发现了likeUserscommentUsers的字段,冷静思考就知道应该就是对应的赞和评论列表。我先给自己点个赞看下里面的数据。

likeCount (int): 1
likeUsers (NSMutableArray*): <__NSArrayM: 0x283e37060>

发现赞的数量变为1了,在看下里面的内容

(lldb) po 0x283e37060
<__NSArrayM 0x283e37060>(
Class name: WCUserComment
username: wxid_6913ohfkk7kq12
nickname: xia0
content:
source: 0
type: 1
createTime: 1564225007
isLocalAdded: 0
commentID: (null)
comment64ID: (null)
refCommentID: (null)
refComment64ID: (null)
refUserName:
bDeleted: 0
)

正是我自己的微信号。同理可以得到评论

commentCount (int): 1
commentUsers (NSMutableArray*): <__NSArrayM: 0x283e349c0>

(lldb) po 0x283e349c0
<__NSArrayM 0x283e349c0>(
Class name: WCUserComment
username: wxid_6913ohfkk7kq12
nickname: xia0
content: 评论测试
source: 0
type: 2
createTime: 1564225144
isLocalAdded: 1
commentID: (null)
comment64ID: (null)
refCommentID: (null)
refComment64ID: (null)
refUserName: (null)
bDeleted: 0
)

到这里我们还可以发现赞和评论都是一个类(模型),只是里面的类型字段不同。现在我们其实已经拿到了我们想要的数据模型了。但是还有一个问题在于我们应该什么时候去更改这些数据呢?也就是我们说的hook点。

最好的hook可以想到是每次刷新数据的时候,这样我们的数据就是最新的。

寻找HOOK点

先想一下,刷新数据的时候,当拿到新的数据肯定会封装为一个WCDataItem对象,那么我们可以对WCDataItem里面的方法下断点,然后打印调用链不就反向得到了刷新的函数了吗?

但是逆向和调试过微信的人都知道,当你使用bt命令的时候只能得到一堆无符号的调用栈像下面这样

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x000000010857d4d0 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 30312888
    frame #1: 0x0000000194fcc638 Foundation`_decodeObjectBinary + 2004
    frame #2: 0x0000000194fcbb6c Foundation`_decodeObject + 340
    frame #3: 0x0000000194ed24fc Foundation`-[NSKeyedUnarchiver decodeObjectForKey:] + 228
    frame #4: 0x0000000194f2a09c Foundation`+[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
    frame #5: 0x0000000105a88404 WeChat`int fmt::internal::CharTraits<char>::format_float<long double>(char*, unsigned long, char const*, unsigned int, int, long double) + 2432992
    frame #6: 0x0000000108da9ea8 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38884240
    frame #7: 0x0000000108daa890 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38886776
    frame #8: 0x0000000108dad178 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38897248
    frame #9: 0x0000000108717394 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 31991932

但是用我写的sbt命令就能恢复oc符号,下面我们对-[WCDataItem setCid:]下断点,然后得到调用栈

(lldb) sbt
==========================================xia0LLDB==========================================
  BlockSymbolFile    Not Set The Block Symbol Json File, Try 'sbt -f'
============================================================================================
  frame #0: [file:0x103c094d0 mem:0x10857d4d0] WeChat`-[WCDataItem setCid:] + 0
  frame #1: [file:0x18193c638 mem:0x194fcc638] Foundation`_decodeObjectBinary + 2004
  frame #2: [file:0x18193bb6c mem:0x194fcbb6c] Foundation`_decodeObject + 340
  frame #3: [file:0x1818424fc mem:0x194ed24fc] Foundation`-[NSKeyedUnarchiver decodeObjectForKey:] + 228
  frame #4: [file:0x18189a09c mem:0x194f2a09c] Foundation`+[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
  frame #5: [file:0x101114404 mem:0x105a88404] WeChat`+[CUtility SafeUnarchiveFromData:] + 64
  frame #6: [file:0x104435ea8 mem:0x108da9ea8] WeChat`-[WCAdvertiseDataHelper hasCommented:] + 116
  frame #7: [file:0x104436890 mem:0x108daa890] WeChat`-[WCAdvertiseDataHelper IsAdvertiseDataValid:] + 48
  frame #8: [file:0x104439178 mem:0x108dad178] WeChat`-[WCAdvertiseDataHelper getAdvertiseDataByCurMinTime:MaxTime:] + 552
  frame #9: [file:0x103da3394 mem:0x108717394] WeChat`Maybe c function? Distance:3348 >= 2500 # Symbol:-[WCTimelineMgr tryRemoveCachesOfLikeUserWithNewTimelineList:] + 3348
  frame #10: [file:0x206a0 mem:0x1131f06a0] WeChat`-[WCTimelineMgr onDataUpdated:andData:andAdData:withChangedTime:] + 233
  frame #11: [file:0x103db00c8 mem:0x1087240c8] WeChat`Maybe c function? Distance:9732 >= 2500 # Symbol:-[WCTimelineDataProvider responseForSnsTimeLineResponse:Event:] + 9732
  frame #12: [file:0x103db0398 mem:0x108724398] WeChat`-[WCTimelineDataProvider MessageReturn:Event:] + 112
  frame #13: [file:0x1033923c0 mem:0x107d063c0] WeChat`-[CAppObserverCenter NotifyFromMainCtrl:Event:] + 336
  frame #14: [file:0x104c292f8 mem:0x10959d2f8] WeChat`-[CMainControll TimerCheckEvent] + 728
  frame #15: [file:0x1800a3604 mem:0x193733604] libobjc.A.dylib`-[NSObject performSelector:withObject:] + 68
  frame #16: [file:0x101cb1fa8 mem:0x106625fa8] WeChat`-[MMNoRetainTimerTarget onNoRetainTimer:] + 84
  frame #17: [file:0x1819750bc mem:0x1950050bc] Foundation`__NSFireTimer + 88
  frame #18: [file:0x180e3d0a4 mem:0x1944cd0a4] CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32
  frame #19: [file:0x180e3cdd0 mem:0x1944ccdd0] CoreFoundation`__CFRunLoopDoTimer + 884
  frame #20: [file:0x180e3c5c4 mem:0x1944cc5c4] CoreFoundation`__CFRunLoopDoTimers + 252
  frame #21: [file:0x180e37284 mem:0x1944c7284] CoreFoundation`__CFRunLoopRun + 1832
  frame #22: [file:0x180e36844 mem:0x1944c6844] CoreFoundation`CFRunLoopRunSpecific + 452
  frame #23: [file:0x1830e5be8 mem:0x196775be8] GraphicsServices`GSEventRunModal + 104
  frame #24: [file:0x1ae78431c mem:0x1c1e1431c] UIKitCore`UIApplicationMain + 216
  frame #25: [file:0x100152b04 mem:0x104ac6b04] WeChat`main + 1387268
  frame #26: [file:0x1808ec020 mem:0x193f7c020] libdyld.dylib`start + 4

可以看到调用栈的符号已经恢复了,能够清晰的看出调用的过程

其中里面有个很明显的方法

-[WCTimelineMgr onDataUpdated:andData:andAdData:withChangedTime:]

看名字就知道,这个应该就是我们需要的hook点。

整理思路

整理一下目前的情况,首先拿到了赞和评论的模型,然后找到了hook点。下一步就是写代码去实现集赞的功能。

大概的代码逻辑应该如下

  • 在hook点的时候拿到原始的朋友圈数据,并过滤出自己的那条朋友圈
  • 取出自己朋友圈的赞和评论数据备用
  • 随机从通讯录好友里面选择数量去构造赞和评论对象,并放入原朋友圈赞和评论列表里面

下面就是写代码实现就可以了。还有个情况是在你进入自己的朋友圈详情界面的时候,也就是看到点赞的人都是头像的界面。也需要做类似的操作才能实现集赞的功能。

这里分析的过程和上面类似,我选择的hook点为:
-[WCCommentDetailViewControllerFB setDataItem:]

代码实现

具体的代码实现这里就不再去分析了,我把代码开源到了这里fkwechatzan

完成效果

  • 集赞助手设置界面

  • 朋友圈详情界面

  • 赞和评论

一点总结

本文详细介绍了使用issh和xia0LLDB去完成一个集赞功能的逆向分析过程,这个功能本身不是很复杂,这里仅仅我在逆向过程中的一些理解和分析。每个人的逆向分析过程可能都不尽相同,我提供一个完整的分析步骤,而不是完全的去靠猜测,虽然逆向有时候猜测就能有一些意外惊喜,不过不确定性也同样会花费大量时间。

最后,妈妈再也不用担心没有人赞我的朋友圈了~

下次集赞领礼品的活动我要定了!