1.申请ID:花落人断肠2.个人邮箱:chacewan@163.com3.在论坛潜水这么多年,每次都错过开发注册的时间,这次静下心来做一个wx获取朋友圈数据的ios小逆向案例,给朋友们提供一个思路。开始App Configurator 2 中导出 wx ,frIDA-ios-dump 脱壳,class-dump 导出 header,MonkeyDev 创建项目。
分析首先,朋友圈的数据分析当然是从朋友圈的UI开始分析是最好的,使用Reveal很快能定位到朋友圈是WCTimeLineViewController,通过导出头文件可以找到WCTimeLineViewController.h。从图中可以看到WCTimeLineViewController中有实现tableViewDelegate代{过}{滤}理:[img]blob:https://www.52pojie.cn/c0e43b04-d6c0-4ad3-abe8-2f1bf9fd5f90[/img]
此时,感觉就应该要从 UITableView 的数据源入手。Xcode 运行 Debug wx,在朋友圈界面暂停,Console 窗口中打印 WCTimeLineViewController 中猜测有可能是数据源的变量名,简单尝试几个后并没有什么发现。打算反编译程序的逻辑来入手。众所周知,UITableView 一般情况下是使用 tableView:cellForRowAtIndexPath: 设置 Cell 的数据,从这个函数下手,大概率就可以知道数据从哪里来的了。启动 Hopper 导入 wx 二进制文件,搜索 WCTimeLineViewController tableView:cellForRowAtIndexPath: 查看伪代码(截图为关键部分):[WCTimeLineViewController tableView:cellForRowAtIndexPath:] 数据源部分[img]blob:https://www.52pojie.cn/832de635-19b9-4b06-b3b6-2efcffda4919[/img]
观察到函数 getTimelineDataItemOfIndex 很可能是数据来源,通过函数名 DataItem 做为线索,在导出的 Header 中搜索到 WCDataItem,查看 WCDataItem 推测应该就是朋友圈使用的数据结构。根据上下文可知获取数据的方式是:
[Objective-C] 纯文本查看 复制代码 MMContext *context = [MMContext currentContext];WCFacade *facade = [context getService:[WCFacade class]];WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:index];
没想到这么容易就找到数据源了,继续测试:
[Objective-C] 纯文本查看 复制代码 %hook WCTimeLineViewController
- (void)viewDidLoad { // [font="]读取朋友圈数据[/font] MMContext *context = [%c(MMContext) currentContext]; WCFacade *facade = [context getService:[%c(WCFacade) class]]; WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0]; // --- [font="]此处断点[/font] --- // dataItem [font="]为[/font] nil %orig();}
%end
运行!进入朋友圈后断点,查看 dataItem 变量,怎么 dataItem 是 nil?再回头看看 Hopper 的代码,看上去似乎没问题啊,这时候尝试继续运行程序,再次暂停程序,控制台打印获取 WCDataItem:[Objective-C] 纯文本查看 复制代码 (lldb) po [[[MMContext currentContext] getService:[WCFacade class]] getTimelineDataItemOfIndex:0]
Class name: WCDataItem, addr: 0x10e3b8e80tid: xxxxxxxxxxxxxxxusername: xxxxxxxxxxcreatetime: xxxxxxxx... 成功了,发现只能在朋友圈界面加载完之后,才可以获取通过 getTimelineDataItemOfIndex 获取到数据,到底是缺少了哪一步呢?
继续深挖首先,尝试修改代码,将%orig()移动到获取数据之前:
[Objective-C] 纯文本查看 复制代码 %hook WCTimeLineViewController
- (void)viewDidLoad { %orig(); // [font="]读取朋友圈数据[/font] MMContext *context = [%c(MMContext) currentContext]; WCFacade *facade = [context getService:[%c(WCFacade) class]]; WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0]; // --- [font="]此处断点[/font] --- // dataItem [font="]有数据[/font]}
%end
再次运行,成功获取数据,由此猜测,在 viewDidLoad 中存在某些代码,是使用 getTimelineDataItemOfIndex 的前提条件。在 Hopper 查看 viewDidLoad 伪代码(代码太长就不贴了),首先搜索 MMContext 关键词,发现只有语言和主题相关内容。再仔细看发现有一个诱人的函数:[img]blob:https://www.52pojie.cn/b5e7826d-9d55-4474-8f66-2b41a05dc951[/img]为了验证 initData 是否是下一步分析的关键,先尝试 hook 它,内部实现为空,这样就相当于不执行 initData 了:
[Objective-C] 纯文本查看 复制代码 %hook WCTimeLineViewController
- (void)initData {}
%end
运行,发现在 initData 不执行任何代码后,朋友圈加载不出数据,这样可以针对它下手了。查看伪代码,发现这里 调用了 WCFacade 的 beginTimeline:[img]blob:https://www.52pojie.cn/23d81a9d-3e65-425c-bb99-3a18e38883c3[/img]将beginTimeline加入到initData:
[Objective-C] 纯文本查看 复制代码 %hook WCTimeLineViewController
- (void)initData { MMContext *context = [%c(MMContext) currentContext]; WCFacade *facade = [context getService:[%c(WCFacade) class]]; [facade beginTimeline];}
%end
这时候朋友圈数据又出现了,那么 dataItem 为 nil 仅仅是因为缺少 beginTimeline 吗?在 beginTimeline 之后获取数据试试:
[Objective-C] 纯文本查看 复制代码 %hook WCTimeLineViewController
- (void)initData {MMContext *context = [%c(MMContext) currentContext];WCFacade *facade = [context getService:[%c(WCFacade) class]];[facade beginTimeline];WCDataItem *dataItem = [facade getTimelineDataItemOfIndex:0];// --- [font="]此处断点[/font] ---// dataItem [font="]为[/font] nil}
%end
运行结果 dataItem 为 nil,但朋友圈是能正常显示,推断 beginTimeline 是显示朋友圈的必要但非唯一条件(推断错了,看后面)。只能回到 viewDidLoad 继续探索其他条件。再次检查 viewDidLoad 仅仅从代码函数命名上并没有什么发现,此时只能转战动态调试。
动态调试在这种没头绪的时刻,我想就是动态调试发挥的时候了。打开 Hopper,在 viewDidLoad 中大概找几个位置打上断点,想用于缩小范围来确定是哪个位置执行后, getTimelineDataItemOfIndex 能获取到值:
[Objective-C] 纯文本查看 复制代码 // [font="]输出[/font] ASLR[font="],只需要看[/font] wx [font="]的地址[/font](lldb) im li -o -f
[font=Times]// [/font]打上断点(lldb) br s -a [hopper [font="]中查看的地址[/font]]+[ASLR]
[font=Times]// [/font]执行到断点处再[font=Times] po [/font]获取数据(lldb) po [[[MMContext currentContext] getService:[WCFacade class]] getTimelineDataItemOfIndex:0]
经过几轮断点,发现 getTimelineDataItemOfIndex 的值从 nil 变为有值时所在的位置并不固定,这才发现自己思考的并不全面,此前一直以为的是 beginTimeline 是同步执行,而其实 beginTimeline 是异步的,下一步立刻去获取 getTimelineDataItemOfIndex 时,数据还没有从本地数据库中读取出来(猜测,技术菜,还没有针对 beginTimeline 去深究)。
逆向结果最终,为了检测上一步读取数据是异步的猜想,将 getTimelineDataItemOfIndex 延迟去执行:
[Objective-C] 纯文本查看 复制代码 // [font="]在发现界面显示时获取数据[/font]
@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];// --- [font="]断点[/font] ---// dataItem [font="]有数据[/font]}
%end
成功!目前只是简单获取到已经加载到本地的数据,并且没有对 beginTimeline(还有 endTimeline)机制深究,在逆向过程中发现会在朋友圈界面的 viewDidLoad 中调用 beginTimeline、dealloc 中调用 endTimeline。另外如果需要从网络加载需要更深入的挖掘,按自己理解大概是从朋友圈界面逆向出触发请求数据的函数,然后再通过本文的方法读取出来。 |