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

开始

为什么要做这个集赞的插件呢?起因是上周去参加了某个会议,有一个集赞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: 13121667995275040000
username: wxid_6913ohfkk7xxxx
createtime: 1564224719
commentUsers: (
)
contentObj: <WCContentItem: 0x2801f5500>

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

(lldb) ivars 0x13e2871d0
<WCDataItem: 0x13e2871d0>:
in WCDataItem:
	cid (int): 0
	tid (NSString*): @"13121667995275040000"
	type (int): 0
	flag (int): 0
	username (NSString*): @"wxid_6913ohfkk7xxxx"
	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_6913ohfkk7xxxx
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_6913ohfkk7xxxx
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

Bigboss源直接搜索fkwechatzan即可安装

完成效果

  • 集赞助手设置界面

  • 朋友圈详情界面

  • 赞和评论

一点总结

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

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

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

10 个赞

666
(好像发了两个主题, 我删一个.

嗯,我以为没发成功:joy:

我比较关心怎么过微信检测的:rofl:

哈哈哈哈哈,好玩 :joy:

这个要是普及了,商家要恨死你了:thinking:

2 个赞

哈哈哈,到时候就说是狗神你传授的技术:smiling_imp:

今天封号了吗
:cold_face:

1 个赞

牛牛牛!!

onDataUpdated的arg4是int型,用id部分机型会闪退。

1 个赞

这个就很实用了~

学习一波。

大佬,界面启动在哪里呀,桌面没有,微信里也没看到,设置里也没有。是我安装的姿势不对吗。

:+1:是的,我本地的代码这里改了的,还没更新到github

就在微信设置里面,没看到估计你没安装成功。建议你直接从cydia源里面安装试试

还有种情况可能是你的微信版本过低,建议更新到最新版的微信:sweat_smile:

找到啦,谢谢老大。

额,这个赞和评论是只有自己看的到的,等于自慰把?什么场景下需求的啊

本地造出来了,给商家看一下,就可以享受优惠了

1 个赞

真的能成功领到??我这里的商家都是让分享朋友圈。学到了。

一般来说商家就看下就可以了(线上or线下都行),难不成他还加你的好友来看下你发的朋友圈?(但这个插件普遍了的话,商家说不定还真有可能这么做:sweat_smile:

1 个赞