开始
为什么要做这个集赞的插件呢?起因是上周去参加了某个会议,有一个集赞60领玩偶的活动,但是想到平时一条朋友圈也就几个赞,而且又不想找人点赞,领不到,很气。回去以后想着能不能写一个集赞的插件,在需要的时候直接输入想要的赞、评论数量,我发的朋友圈就能有多少赞。这样再有这样的活动岂不美哉。准备开干!
理性分析
再开始之前,先理性分析一波:如果想要集赞,这里有两种思路,一个是直接在view层去更改,但是这样得去处理界面的一些细节,一旦不注意,很容易崩溃。还有种思路是更改datasource或者说赞评论的模型。可以想到,最初一条朋友圈肯定是从服务器拿到数据并封装成对应的模型。一般来说,越改底层的数据或者说源头的数据,那么稳定性和真实性就更高。这里我的想法就是既不改view层,也不改源头层,就改封装好的模型那一层应该就很符合要求。接下来主要讲一下怎么我去实现这个需求的分析过程,因为本身功能不是很复杂,大佬随便看看就行。
准备条件
- 一台mac
- 一台越狱的iOS设备
- ida/Hooper/theos
- flex/issh/xia0LLDB
- 其他常见逆向工具等
对于一贯喜欢上调试器分析得我,所以写了iSSH和xia0LLDB两个工具,在这两个工具的辅助下整个插件用了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的朋友圈。
里面我们还发现了likeUsers
和commentUsers
的字段,冷静思考就知道应该就是对应的赞和评论列表。我先给自己点个赞看下里面的数据。
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去完成一个集赞功能的逆向分析过程,这个功能本身不是很复杂,这里仅仅我在逆向过程中的一些理解和分析。每个人的逆向分析过程可能都不尽相同,我提供一个完整的分析步骤,而不是完全的去靠猜测,虽然逆向有时候猜测就能有一些意外惊喜,不过不确定性也同样会花费大量时间。
最后,妈妈再也不用担心没有人赞我的朋友圈了~
下次集赞领礼品的活动我要定了!