本人是逆向新手一枚,最近才开始了解 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
逆向拿朋友圈数据的文章。边学工具边看论坛里大神的逆向流程,这才鼓捣成功,受益匪浅。
可能贴子写的很乱,看观受累了~