iOS逆向-微信自动添加好友

前言

上次完成了 macOS 版微信小助手,如今终于有(xian)时(de)间(huang)来讲说 iOS 逆向了。本篇主要实如今微信上自动添加好友(即自动验证新的朋友申请),从而熟悉 iOS 逆向分析的过程,可能总结的有点粗糙,若是有不懂的地方欢迎探讨。python

github地址: iOS 版微信小助手(防撤回、修改微信运动、群管理、好友请求管理)ios

工具

如下工具的详细使用方法能够查看iOS应用逆向工程 第2版 第二部分 工具篇。git

macbook 软件

  • theosgithub

    制做 Tweak 的工具数组

  • hopper disassemblersass

    用于静态分析bash

  • usbmuxd微信

    端口转发,可让咱们经过 usb 链接手机进行 ssh、lldb 调试等。主要使用python-client目录下的文件app

  • class-dumpssh

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地址
复制代码

下载 ldident,进行

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 root@192.168.31.94 若是是wifi链接,请查看当前手机的wifi地址。
复制代码

查看微信的进程信息

ps -e |grep WeChat
复制代码

Cycript 注入

cycript -p WeChat // 或者是当前微信的进程号,以下所示
复制代码

cycript

启动Cycript后,使用如下命令查看当前 UI 布局

UIApp.keyWindow.recursiveDescription().toString()
复制代码

屏幕快照 2017-04-01 上午10.57.28.png

由于知道当前的视图有 tableView,因此找到 tableView 的对象。从上图能够看到该对象的地址为0x18c4be00。 在使用 nextResponder()根据响应者往上找当前的 ViewController。

屏幕快照 2017-04-01 上午11.01.03.png
找到当前的控制器,为 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 配置相关文件(具体查看iOS逆向-微信helloWorld), 而后进行make package install 安装至手机。

从新启动微信进入新的朋友界面。

在ssh中使用ondeviceconsole打印手机的 log。

这时用另外一个微信号添加本身好友。触发好友请求的方法。能够看到如下的 log

屏幕快照 2017-04-01 上午11.22.48.png

说明有好友添加请求的时候,会调用 -[SayHelloViewController OnSayHelloDataChange]

动态分析

既然已经知道了有好友请求的时候会调用OnSayHelloDataChange,那么咱们能够在当前方法中进行处理,然而有个弊端,就是当有好友请求时,微信不在新的朋友界面时,是不会调用该方法的。因此咱们应该在更底层的类中(假设为消息管理类)中进行处理,而怎么找到消息管理类呢?按照通常的逻辑,消息管理类中必定有方法触发了OnSayHelloDataChange,这时候就要用到 lldb + hopper 神器来找到相应的消息管理类与其处理方法了。

lldb 进行手机端调试,hopper 进行静态分析,分析OnSayHelloDataChange方法的信息,找出突破口。

先用 hopper 打开微信的二进制文件。搜索SayHelloViewController OnSayHelloDataChange方法。 能够看到当前方法在微信中的偏移地址0x14a4824。

QQ20170401-113150@2x.png

启动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(这个值不是定值)

QQ20170401-113326@2x.png

经过以上能够找到 [SayHelloViewController OnSayHelloDataChange]方法在手机上的内存地址。即

内存地址 = 进程内存基地址 + 方法偏移地址
复制代码

使用br打断点查看

br s -a "0x000b2000 + 0x14a4824"
复制代码

屏幕快照 2017-04-01 上午11.39.22.png

接着输入c继续运行,从新使用另外一微信帐号添加好友,会触发该断点。

屏幕快照 2017-04-01 上午11.41.35.png

使用bt查看调用栈信息,即哪些方法调用了当前的方法,找到方法的上游。(异步调用的话没办法查看)

QQ20170401-114245@2x.png

第一个表示当前的方法,能够看到在调用此方法前,该进程总共调用了3个方法。 分别计算出这三个方法在微信中的偏移量。

屏幕快照 2017-04-01 上午11.48.13.png

将这三个地址在 hopper 中查看(按快捷键g,输入地址),找到了对应的方法为

// 调用的顺序为从下到上
[SayHelloViewController OnSayHelloDataChange]
[SayHelloDataLogic onFriendAssistAddMsg:]
[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
[CMessageMgr MainThreadNotifyToExt:]
复制代码

从以上方法名能够猜想

[FriendAsistSessionMgr OnAddMsgForSpecialSession:MsgList:]
复制代码

是用来接收添加好友消息的函数处理,其中MsgList:后面的参数可能为消息的数组,为了证实咱们能够在该方法中打个断点查看下。 使用命令register read读取寄存器地址,并使用po打印该对象。

屏幕快照 2017-04-01 下午2.05.24.png

看出r3寄存器确实是个数组,同时也获得了消息的对象为CMessageWrap 证实咱们是对的。

ps: 解释下为何要看r3,由于在 armv7 中,一个方法的调用,通常寄存器都是这么存储的:前四个参数放在r0~r3,剩下的存放在堆栈中。查看堆栈的话使用x/10 $sp 查看前10个堆栈里的对象地址。

然而FriendAsistSessionMgr这个类可能在新的好友界面进行一些初始化,且放在SayHelloViewController中,而咱们想要的是无论在哪一个控制器里均可以 hook 住上面的消息数组对象。所以咱们往上找,[CMessageMgr MainThreadNotifyToExt:],然而里面并无咱们须要的信息。而根据类名咱们推测CMessageMgr是用来管理消息的。有多是在异步执行了消息数组的获取。

所以,重复以上步骤,使用 logify 对CMessageMgr进行 Log 分析。最终锁定了 CMessageMgr MessageReturn:MessageInfo:Event:

屏幕快照 2017-04-05 下午3.02.50.png

定位经过好友请求的方法

动态分析

既然找到了接收好友请求的方法,那么是时候找经过好友请求的方法了。 咱们知道,经过好友请求的方法,是在新的朋友界面,点击接受的时候触发的。(能够经过 Log 分析,然而这里还有另外一个比较快速的方法)

Cycript 定位

先经过 Cycript 打印出全部的 UI 层级。 找到接受按钮的对象,(有个技巧,咱们知道当前按钮是在某个 cell 下面的,因此定位这个)。

WX20170405-152315@2x.png
再经过cycript将该对象的 hidden 动态修改成 1,看是否隐藏。

#0x186922f0.hidden = 1
复制代码

发现按钮不见了,证实咱们是对的。这时候须要找到点击按钮的事件。

而咱们知道 UIButton 是继承 UIControl 的,在 Cycript 中, 能够经过allTargetsallControlEvents查看当前UIControl全部的targets与events,再使用actionsForTarget:forControlEvent:从而找到触发的方法。

屏幕快照 2017-04-05 下午3.30.08.png

看出所触发的方法为[ContactsItemView onRightBtnAction]

静态分析

既然拿到了方法名,那咱们怎么看他具体的实现呢? 接下来就是大名鼎鼎的 hopper 登场了。 用 hopper 打开微信的二进制文件,并进行汇编与伪代码的转换。 ~~因为汇编读起来比较晦涩,因此仍是进行伪代码的转换,这样效率比较快。~~点击该按钮进行转换

QQ20170401-094655@2x.png

能够获得伪代码

屏幕快照 2017-04-05 下午3.56.21.png
上图咱们看到了

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:]. 而 ContactsItemViewdelegateSayHelloViewController

再用 hopper 定位onContactsItemViewRightButtonClick

屏幕快照 2017-04-05 下午4.05.49.png
屏幕快照 2017-04-05 下午4.14.09.png

看到这里估计会很懵逼不知道从何下手。这时候只要加以推测就能够了。 上图中进行了两个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:方法。主要的部分以下所示。

Paste_Image.png

经过分析,咱们能够获得,确认好友申请,显示构造了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 分析。

参考

iOS应用逆向工程 第2版
移动App入侵与逆向破解技术-iOS篇