iOS逆向-微信自动添加好友
前言
上次完成了 macOS 版微信小助手,现在终于有(xian)时(de)间(huang)来说说 iOS 逆向了。本篇主要实现在微信上自动添加好友(即自动验证新的朋友申请),从而熟悉 iOS 逆向分析的过程,可能总结的有点粗糙,如果有不懂的地方欢迎探讨。
github地址: iOS 版微信小助手(防撤回、修改微信运动、群管理、好友请求管理)
工具
以下工具的详细使用方法可以查看iOS应用逆向工程 第2版 第二部分 工具篇。
macbook 软件
- theos
制作 Tweak 的工具
- hopper disassembler
用于静态分析
- usbmuxd
端口转发,可以让我们通过 usb 连接手机进行 ssh、lldb 调试等。
主要使用python-client
目录下的文件 - class-dump
dump 目标对象的 class 信息的工具.
- lldb
调试神器,用过的都说好。默认自带,在
/Applications/Xcode.app/Contents/Developer/usr/bin/lldb
中。
越狱 iPhone 软件
以下软件在 Cydia 中即可下载(debugserver 除外)
- OpenSSH
实现在越狱手机上远程进行 ssh 服务,通过 ssh,即可以通过终端连接 iPhone 进行控制。
-
**iOS 工具大部分都需要在 ssh 环境中使用**
-
复制代码
- Cycript
脚本语言,用于 hook 正在运行的进程,并实时注入代码。
- ondeviceconsole
用于在 Terminal 中查看手机的 log
- debugserver
用于连接手机进行 lldb 调试的工具。用 Xcode 在手机上进行 app 调试即可在iPhone目录的
/Developer/usr/bin/
中生成。
使用 debugserver 需要先进行处理。因为缺少task_for_pid
权限,所以调试不了其他的 app。 先通过 ssh 拷贝 debugserver
-
scp root@iOSIP:/Developer/usr/bin/debugserver ~/debugserver // iOSIP 为手机的ip地址
-
复制代码
下载 ldid 与 ent,进行
-
ldid – Sent.xml debugserver
-
复制代码
在使用 ssh 拷贝至手机,完成。
分析
思路:想要实现自动验证好友请求,则要拿到获取好友请求的方法,以及通过好友请求的方法。hook 获取好友请求的方法,在接收到好友请求的时候,执行添加好友的方法。 而这些主要逻辑在“新的朋友”界面。
定位好友请求的方法
UI 分析
我们知道,根据 MVC 模式,一般的方法实现都是在 ViewController 中的,所以想要拿到好友请求的方法,要先拿到当前界面的控制器。而这时候可以通过 UI 分析获得。
先打开新的朋友界面。
使用 usbmuxd
进行端口的转发(若手机不卡,可以跳过这步直接使用 ssh 进行 wifi 远程连接)
-
python tcprelay.py -t 22:2222
-
复制代码
再使用 ssh 连接至手机
-
ssh root@localhost -p 2222
-
// ssh [email protected] 如果是wifi连接,请查看当前手机的wifi地址。
-
复制代码
查看微信的进程信息
-
ps -e |grep WeChat
-
复制代码
Cycript 注入
-
cycript -p WeChat // 或者是当前微信的进程号,如下所示
-
复制代码
启动Cycript
后,使用以下命令查看当前 UI 布局
-
UIApp.keyWindow.recursiveDescription().toString()
-
复制代码
因为知道当前的视图有 tableView,所以找到 tableView 的对象。从上图可以看到该对象的地址为0x18c4be00。 在使用 nextResponder()
根据响应者往上找当前的 ViewController。
找到当前的控制器,为 SayHelloViewController
Log 分析
使用class-dump
dump 出微信的 class 信息。
-
class-dump -S -s -H demo.app -o ~/Document/headers/
-
// dump 微信app的头文件保存在 ~/Document/headers/ 目录中
-
复制代码
再使用 theos 的 logify 工具,该工具用来注入NSLog
来打印方法的入参和出参。(就是在 hook 某个类的所有的方法,并在里面加 log,并导出xm文件)
-
logify.pl ~/Document/headers/SayHelloViewController.h > ~/Desktop/Tweak.xm
-
复制代码
**注意:**一般该Tweak.xm仍然无法执行,需要进行修改:
- 去掉 .cxx_destruct 方法
- 将 HBLogDebug 改为 NSLog
- 去掉所有的 delegate
- 将所有的参数对象类型改成 id
- 去掉所有的 weak
再使用 theos 配置相关文件, 然后进行make package install 安装至手机。
重新启动微信进入新的朋友界面。
在ssh中使用ondeviceconsole
打印手机的 log。
这时用另一个微信号添加自己好友。触发好友请求的方法。可以看到以下的 log
说明有好友添加请求的时候,会调用 -[SayHelloViewController OnSayHelloDataChange]
动态分析
既然已经知道了有好友请求的时候会调用OnSayHelloDataChange
,那么我们可以在当前方法中进行处理,然而有个弊端,就是当有好友请求时,微信不在新的朋友界面时,是不会调用该方法的。所以我们应该在更底层的类中(假设为消息管理类)中进行处理,而怎么找到消息管理类呢?按照一般的逻辑,消息管理类中一定有方法触发了OnSayHelloDataChange
,这时候就要用到 lldb + hopper 神器来找到相应的消息管理类与其处理方法了。
lldb 进行手机端调试,hopper 进行静态分析,分析OnSayHelloDataChange
方法的信息,找出突破口。
先用 hopper 打开微信的二进制文件。搜索SayHelloViewController OnSayHelloDataChange
方法。 可以看到当前方法在微信中的偏移地址0x14a4824。
启动debugserver 配合lldb调试
先打开微信,并使用 usbmuxd 转换端口
-
python tcprelay.py -t 1234:1234
-
复制代码
再 ssh 到手机上,开启 debugserver 。
-
debugserver *:1234 -a “WeChat”
-
复制代码
使用新的 Terminal 窗口,打开 lldb,连接1234端口,并查看当前微信的进程信息(一般会在所有进程的首行)。 此时会卡住一段时间。
-
// 打开lldb
-
/Applications/Xcode.app/Contents/Developer/usr/bin/lldb
-
// 连接端口调试
-
(lldb) process connect connect://localhost:1234
-
// 打印所有进程
-
(lldb) image list -o -f
-
复制代码
找到微信在当前手机上的进程内存基地址为0x000b2000(这个值不是定值)
通过以上可以找到 [SayHelloViewController OnSayHelloDataChange]
方法在手机上的内存地址。即
-
内存地址 = 进程内存基地址 + 方法偏移地址
-
复制代码
使用br
打断点查看
-
br s -a “0x000b2000 + 0x14a4824”
-
复制代码
接着输入c
继续运行,重新使用另一微信账号添加好友,会触发该断点。
使用bt
查看调用栈信息,即哪些方法调用了当前的方法,找到方法的上游。(异步调用的话没办法查看)
*个表示当前的方法,可以看到在调用此方法前,该进程总共调用了3个方法。 分别计算出这三个方法在微信中的偏移量。
将这三个地址在 hopper 中查看(按快捷键g,输入地址),找到了对应的方法为
-
// 调用的顺序为从下到上
-
[SayHelloViewController OnSayHelloDataChange]
-
[SayHelloDataLogic onFriendAssistAddMsg:]
-
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
-
[CMessageMgr MainThreadNotifyToExt:]
-
复制代码
从以上方法名可以猜测
-
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
-
复制代码
是用来接收添加好友消息的函数处理,其中MsgList:
后面的参数可能为消息的数组,为了证明我们可以在该方法中打个断点查看下。 使用命令register read
读取寄存器地址,并使用po
打印该对象。
看出r3寄存器确实是个数组,同时也得到了消息的对象为CMessageWrap
证明我们是对的。
ps: 解释下为什么要看r3,因为在 armv7 中,一个方法的调用,一般寄存器都是这么存储的:前四个参数放在r0~r3,剩下的存放在堆栈中。查看堆栈的话使用x/10 $sp
查看前10个堆栈里的对象地址。
然而FriendAsistSessionMgr
这个类可能在新的好友界面进行一些初始化,且放在SayHelloViewController
中,而我们想要的是不管在哪个控制器里都可以 hook 住上面的消息数组对象。因此我们往上找,[CMessageMgr MainThreadNotifyToExt:]
,然而里面并没有我们需要的信息。而根据类名我们推测CMessageMgr
是用来管理消息的。有可能是在异步执行了消息数组的获取。
因此,重复以上步骤,使用 logify 对CMessageMgr
进行 Log 分析。*终锁定了 CMessageMgr MessageReturn:MessageInfo:Event:
定位通过好友请求的方法
动态分析
既然找到了接收好友请求的方法,那么是时候找通过好友请求的方法了。 我们知道,通过好友请求的方法,是在新的朋友界面,点击接受的时候触发的。(可以通过 Log 分析,然而这里还有另一个比较快速的方法)
Cycript 定位
先通过 Cycript 打印出所有的 UI 层级。 找到接受按钮的对象,(有个技巧,我们知道当前按钮是在某个 cell 下面的,所以定位这个)。
再通过cycript将该对象的 hidden 动态修改为 1,看是否隐藏。
-
#0x186922f0.hidden = 1
-
复制代码
发现按钮不见了,证明我们是对的。这时候需要找到点击按钮的事件。
而我们知道 UIButton 是继承 UIControl 的,在 Cycript 中, 可以通过allTargets
与 allControlEvents
查看当前UIControl所有的targets与events,再使用actionsForTarget:forControlEvent:
从而找到触发的方法。
看出所触发的方法为[ContactsItemView onRightBtnAction]
静态分析
既然拿到了方法名,那我们怎么看他具体的实现呢? 接下来就是大名鼎鼎的 hopper 登场了。 用 hopper 打开微信的二进制文件,并进行汇编与伪代码的转换。 ~~由于汇编读起来比较晦涩,所以还是进行伪代码的转换,这样效率比较快。~~点击该按钮进行转换
可以得到伪代码
上图我们看到了
-
r10 = self;
-
r5 = r10 + *0x33befe8;
-
r4 = objc_loadWeakRetained(r5);
-
r8 = @selector(onContactsItemViewRightButtonClick:);
-
r11 = [r4 respondsToSelector:r8];
-
复制代码
可以得出,r11 = [r5 onContactsItemViewRightButtonClick:btn]
,而 r5 我们判断为 self 的代理,这个我们也可以通过在之前用 class-dump 的头文件里面搜索onContactsItemViewRightButtonClick
,会发现在ContactsItemViewDelegate
中。 也就是[ContactsItemView onRightBtnAction]
内部调用了[self.delegate onContactsItemViewRightButtonClick:]
. 而 ContactsItemView
的delegate
为 SayHelloViewController
。
再用 hopper 定位onContactsItemViewRightButtonClick
。
看到这里估计会很懵逼不知道从何下手。这时候只要加以推测就可以了。 上图中进行了两个if判断,*个为
-
r10 = @selector(class);
-
r2 = loc_1c099bc(@class(CPushContact), r10);
-
r1 = @selector(isKindOfClass:);
-
r5 = loc_1c099bc(r4, r1, r2);
-
loc_1c099d4(r4);
-
if ((r5 & 0xff) != 0x0) {
-
复制代码
可以得出其实是执行了 if([r4 isKindOfClass:[CPushContact class]])
; 而r4是什么呢?可以肯定是CPushContact
对象,不然下面的代码都不执行了。我们可以根据动态分析,通过 lldb 打断点,并查看r3寄存器的对象类型,可以看到该对象为CPushContact
对象。因此r4就是CPushContact
对象,根据字面意思可以得到就是联系人对象。
继续看下面的代码,可以看到也进行了一次判断if (((loc_1c099bc(r6, @selector(m_bSuspiciousUser)) & 0xff) != 0x0) && ((loc_1c099bc(r6, @selector(isMMContact)) & 0xff) == 0x0))
,看到了MMUIAlertView
。推测是弹窗的 view ,推测如果是可疑的用户或者当前申请的好友已经是自己的好友,那就进行弹窗。而另一部分为verifyContactWithOpCode:opcode:
,推测该部分为添加好友的方法。 可以通过 Log 分析或者通过 lldb 打断点,会看到都会进入该方法。且参数分别为CPushContact
对象与 3。 接着继续分析verifyContactWithOpCode:opcode:
方法。主要的部分如下所示。
通过分析,我们可以得到,确认好友申请,显示构造了CContactVerifyLogic
对象。再构造了一个CVerifyContactWrap
对象,并设置了相关属性,比如m_nsUsrName
m_uiScene
m_nsTicket
.然后通过添加到数组中,通过CContactVerifyLogic
对象的startWithVerifyContactWrap:opCode:parentView:fromChatRoom:
方法发送。 代码如下:
-
CContactVerifyLogic *verifyLogic = [[CContactVerifyLogic alloc] init];
-
CVerifyContactWrap *wrap = [[CVerifyContactWrap alloc] init];
-
[wrap setM_nsUsrName:contact.m_nsEncodeUserName];
-
[wrap setM_uiScene:contact.m_uiFriendScene];
-
[wrap setM_nsTicket:contact.m_nsTicket];
-
[wrap setM_nsChatRoomUserName:contact.m_nsChatRoomUserName];
-
wrap.m_oVerifyContact = contact;
-
-
AutoSetRemarkMgr *mgr = [[MMServiceCenter defaultCenter] getService:[AutoSetRemarkMgr class]];
-
id attr = [mgr GetStrangerAttribute:contact AttributeName:1001];
-
-
if([attr boolValue]) {
-
[wrap setM_uiWCFlag:(wrap.m_uiWCFlag | 1)];
-
}
-
[verifyLogic startWithVerifyContactWrap:[NSArray arrayWithObject:wrap] opCode:3 parentView:[UIView new] fromChatRoom:NO];
-
复制代码
这样我们就得到了 获取好友请求的方法与添加好友的方法。 而这里还有一个问题,就是添加好友的对象是CPushContact
,而获得好友请求的对象的CMessageWrap
。这里需要进行转换,而转换的方法也在SayHelloViewController
中,可以重复上面的分析方法获得。
编写Tweak
通过以上的分析,将代码合并起来
-
%hook CMessageMgr
-
– (void)MessageReturn:(unsigned int)arg1 MessageInfo:(NSDictionary *)info Event:(unsigned int)arg3 {
-
%orig;
-
if (arg1 == 332) { // 收到添加好友消息
-
NSString *keyStr = [info objectForKey:@“5”];
-
if ([keyStr isEqualToString:@“fmessage”]) {
-
NSArray *wrapArray = [info objectForKey:@“27”];
-
[self addAutoVerifyWithArray:wrapArray];
-
}
-
}
-
}
-
-
%new
-
– (void)addAutoVerifyWithArray:(NSArray *)ary {
-
[ary enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
-
CPushContact *contact = [%c(SayHelloDataLogic) getContactFrom:obj];
-
if (![contact isMyContact] && [contact.m_nsDes isEqualToString:autoVerifyKeyword]) {
-
CContactVerifyLogic *verifyLogic = [[%c(CContactVerifyLogic) alloc] init];
-
CVerifyContactWrap *wrap = [[%c(CVerifyContactWrap) alloc] init];
-
[wrap setM_nsUsrName:contact.m_nsEncodeUserName];
-
[wrap setM_uiScene:contact.m_uiFriendScene];
-
[wrap setM_nsTicket:contact.m_nsTicket];
-
[wrap setM_nsChatRoomUserName:contact.m_nsChatRoomUserName];
-
wrap.m_oVerifyContact = contact;
-
-
AutoSetRemarkMgr *mgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(AutoSetRemarkMgr)];
-
id attr = [mgr GetStrangerAttribute:contact AttributeName:1001];
-
-
if([attr boolValue]) {
-
[wrap setM_uiWCFlag:(wrap.m_uiWCFlag | 1)];
-
}
-
[verifyLogic startWithVerifyContactWrap:[NSArray arrayWithObject:wrap] opCode:3 parentView:[UIView new] fromChatRoom:NO];
-
}
-
}];
-
}
-
复制代码
总结
本文为本人根据iOS应用逆向工程 第2版的内容进行分析,由于整个逆向流程有点繁琐,有时候也不是只要分析一次就可以成功的,需要反反复复的进行UI分析、Log分析、lldb 分析。