新版 wx 朋友圈数据逆向过程记录

本人是逆向新手一枚,最近才开始了解 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.14frida-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 不执行任何代码后,朋友圈加载不出数据,这样可以针对它下手了。查看伪代码,发现这里 调用了 WCFacadebeginTimeline

beginTimeline加入到initData

%hook WCTimeLineViewController

- (void)initData {
    MMContext *context = [%c(MMContext) currentContext];
    WCFacade *facade = [context getService:[%c(WCFacade) class]];
    [facade beginTimeline];
}

%end

这时候朋友圈数据又出现了,那么 dataItemnil 仅仅是因为缺少 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

运行结果 dataItemnil,但朋友圈是能正常显示,推断 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 中调用 beginTimelinedealloc 中调用 endTimeline

另外如果需要从网络加载需要更深入的挖掘,按自己理解大概是从朋友圈界面逆向出触发请求数据的函数,然后再通过本文的方法读取出来。

虽然只是一个小功能的逆向,但第一次达到目的成就感满满的,而且网络上也没有找到针对7.0.14逆向拿朋友圈数据的文章。边学工具边看论坛里大神的逆向流程,这才鼓捣成功,受益匪浅。

可能贴子写的很乱,看观受累了~😂

5 Likes

7.0.14以及最新的7.0.15接口变化来看感觉在重构

学习了! 7.0.15 还是值得研究得

很详细的过程,学习了

是的,之前就是看你的帖子学习,发现获取数据方法已经不能用,只能自己研究了,刚好练习一下。

1 Like

最近这几个版本一直在改,我也是前两天才适配了7.0.15😅

看来微信还是oc写的,还没转swift?