本人是逆向新手一枚,最近才开始了解 iOS 逆向方面知识。本次主要是记录一下逆向 wx 获取朋友圈数据的过程,包含试错的过程。
论坛中有大神讲过获取朋友圈相关逆向分析获取数据的,是基于 [MMServiceCenter defaultCenter] 进行数据获取的,但我使用的是7.0.14版本,发现已经没有 defaultCenter 了,可以写(水)一篇新的文了。
主要是描述逆向的过程,较少描述工具的使用方法。
工具
frida-ios-dump
class-dump
MonkeyDev
Reveal
Hopper v4
Xcode 的 LLDB
过程
App Configurator 2 中导出 wx 7.0.14 ,frida-ios-dump 脱壳,class-dump 导出 header,MonkeyDev 创建项目。(居家流程)
做好以上准备后,正式开始入手分析。
入口
首先,朋友圈数据的分析的入口点当然是通过朋友圈的 UI 开始分析是最好的,使用 Reveal 很快能定位到朋友圈是 WCTimeLineViewController,通过导出的 header 文件找到 WCTimeLineViewController.h。
从中可以看到 WCTimeLineViewController 有实现 tableViewDelegate:
此时,感觉就应该要从 UITableView 的数据源入手。Xcode 运行 Debug wx,在朋友圈界面暂停,Console 窗口中打印 WCTimeLineViewController 中猜测有可能是数据源的变量名,简单尝试几个后并没有什么发现。打算反编译程序的逻辑来入手。
寻找数据来源
众所周知,UITableView 一般情况下是使用 tableView:cellForRowAtIndexPath: 设置 Cell 的数据,从这个函数下手,大概率就可以知道数据从哪里来的了。
启动 Hopper 导入 wx 二进制文件,搜索 WCTimeLineViewController tableView:cellForRowAtIndexPath: 查看伪代码(截图为关键部分):
[WCTimeLineViewController tableView:cellForRowAtIndexPath:] 数据源部分

观察到函数 getTimelineDataItemOfIndex 很可能是数据来源,通过函数名 DataItem 做为线索,在导出的 Header 中搜索到 WCDataItem,查看 WCDataItem 推测应该就是朋友圈使用的数据结构。根据上下文可知获取数据的方式是:
MMContext *context = [MMContext currentContext];
WCFacade *facade = [context getService:[WCFacade class]];
WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:index];
竟然这么容易,迫不及待的编写代码测试:
%hook WCTimeLineViewController
- (void)viewDidLoad {
// 读取朋友圈数据
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0];
// --- 此处断点 ---
// dataItem 为 nil
%orig();
}
%end
运行!进入朋友圈后断点,查看 dataItem 变量,咦,怎么 dataItem 是 nil?
再回头看看 Hopper 的代码,似乎没问题啊,这时候尝试继续运行程序,再次暂停程序,执行获取 WCDataItem:
(lldb) po [[[MMContext currentContext] getService:[WCFacade class]] getTimelineDataItemOfIndex:0]
Class name: WCDataItem, addr: 0x10e3b8e80
tid: xxxxxxxxxxxxxxx
username: xxxxxxxxxx
createtime: xxxxxxxx
...
成功了,发现只能在朋友圈界面加载完之后,才可以获取通过 getTimelineDataItemOfIndex 获取到数据,那么到底是缺少了哪一步呢?
深挖
首先,尝试修改代码,将%orig()移动到获取数据之前:
%hook WCTimeLineViewController
- (void)viewDidLoad {
%orig();
// 读取朋友圈数据
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0];
// --- 此处断点 ---
// dataItem 有数据
}
%end
再次运行,成功获取数据,由此猜测,在 viewDidLoad 中存在某些代码,是使用 getTimelineDataItemOfIndex 的前提条件。在 Hopper 查看 viewDidLoad 伪代码(代码太长就不贴了),首先搜索 MMContext 关键词,发现只有语言和主题相关内容。再仔细看发现有一个诱人的函数:
为了验证 initData 是否是下一步分析的关键,先尝试 hook 它,内部实现为空,这样就相当于不执行 initData 了:
%hook WCTimeLineViewController
- (void)initData {}
%end
运行,发现在 initData 不执行任何代码后,朋友圈加载不出数据,这样可以针对它下手了。查看伪代码,发现这里 调用了 WCFacade 的 beginTimeline:
将beginTimeline加入到initData:
%hook WCTimeLineViewController
- (void)initData {
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
[facade beginTimeline];
}
%end
这时候朋友圈数据又出现了,那么 dataItem 为 nil 仅仅是因为缺少 beginTimeline 吗?在 beginTimeline 之后获取数据试试:
%hook WCTimeLineViewController
- (void)initData {
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
[facade beginTimeline];
WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0];
// --- 此处断点 ---
// dataItem 为 nil
}
%end
运行结果 dataItem 为 nil,但朋友圈是能正常显示,推断 beginTimeline 是显示朋友圈的必要但非唯一条件(推断错了,看后面)。只能回到 viewDidLoad 继续探索其他条件。
再次检查 viewDidLoad 仅仅从代码函数命名上并没有什么发现,此时只能转战动态调试。
动态调试
在这种没头绪的时刻,我想就是动态调试发挥的时候了。打开 Hopper,在 viewDidLoad 中大概找几个位置打上断点,想用于缩小范围来确定是哪个位置执行后, getTimelineDataItemOfIndex 能获取到值:
// 输出 ASLR,只需要看 WeChat 的地址
(lldb) im li -o -f
// 打上断点
(lldb) br s -a [hopper 中查看的地址]+[ASLR]
// 执行到断点处再 po 获取数据
(lldb) po [[[MMContext currentContext] getService:[WCFacade class]] getTimelineDataItemOfIndex:0]
经过几轮断点,发现 getTimelineDataItemOfIndex 的值从 nil 变为有值时所在的位置并不固定,这才发现自己思考的并不全面,此前一直以为的是 beginTimeline 是同步执行,而其实 beginTimeline 是异步的,下一步立刻去获取 getTimelineDataItemOfIndex 时,数据还没有从本地数据库中读取出来(猜测,技术菜,还没有针对 beginTimeline 去深究)。
逆向结果
最终,为了检测上一步读取数据是异步的猜想,将 getTimelineDataItemOfIndex 延迟去执行:
// 在发现界面显示时获取数据
@interface FindFriendEntryViewController : UIViewController
@end
%hook FindFriendEntryViewController
- (void)viewDidLoad {
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
[facade beginTimeline];
[self performSelector:@selector(getDataItem) withObject:nil afterDelay:3];
%orig();
}
%new
- (void)getDataItem {
MMContext *context = [%c(MMContext) currentContext];
WCFacade *facade = [context getService:[%c(WCFacade) class]];
WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0];
// --- 断点 ---
// dataItem 有数据
}
%end
成功:)
目前只是简单获取到已经加载到本地的数据,并且没有对 beginTimeline(还有 endTimeline)机制深究,在逆向过程中发现会在朋友圈界面的 viewDidLoad 中调用 beginTimeline、dealloc 中调用 endTimeline。
另外如果需要从网络加载需要更深入的挖掘,按自己理解大概是从朋友圈界面逆向出触发请求数据的函数,然后再通过本文的方法读取出来。
虽然只是一个小功能的逆向,但第一次达到目的成就感满满的,而且网络上也没有找到针对7.0.14逆向拿朋友圈数据的文章。边学工具边看论坛里大神的逆向流程,这才鼓捣成功,受益匪浅。
可能贴子写的很乱,看观受累了~



