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

前言

上篇文章讲述了Apple公司双重签名机制的原理,而且针对这个原理咱们又学到了一种将别人的.ipa包修改Bundle ID后运行在咱们手机的方法------重签名。
说白了重签名为的是什么?就是为了能让咱们修改了App的逻辑以后还能正常安装到手机而且调试运行,那么接下来这篇文章就是讲述如何去修改App原有的代码逻辑。
注意哟,本篇文章须要具有一些简单的runtime知识。git

代码注入

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

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

新建framework

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

new framework

直接编译一遍

建立好了framework以后直接编译一遍,
而后查看Products--->代码注入.app--->Frameworks文件夹,是否是发现咱们刚刚建立的framework已经添加到Frameworks文件夹里面了。是否是很神奇?这也是为何咱们要使用framework进行代码注入的缘由之一。 服务器

HYHook

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

App在运行的时候可以执行到如下3个地方的代码:微信

  1. 系统库。非越狱手机是没法修改的。
  2. MachO二进制文件。能够修改,可是咱们须要使用二进制去修改,要求较高。
  3. framework库。

在刚刚咱们已经将framework添加到了Frameworks文件夹下,可是注意,这并不表明着这个framework已经能够用了。使用MachOView查看App的MachO文件以下。由于MachO文件的二进制数据的排列是有规律的,因此这里咱们就可使用MachOView来将MachO二进制文件破解出某些信息,在界面上展现出来。 网络

MachOView
上图中MachO文件破解后有个 Load Commands项,这一项中表示了在MachO执行的时候须要加载的资源文件,而下面圈起来的部分就是须要加载的代码库,咱们查看这些库能够发现这里面是没有咱们刚刚新建的framework的。

也就是说,咱们刚刚新建了一个framework,而且也添加到了Frameworks文件夹里面,可是在执行MachO文件的时候并不会将这个framework加载到内存中,所以也就仍是没法调用。此时咱们就须要另一个工具yololib了,这是一个命令行工具,安装后在重签名脚本最后添加下面一行脚本便可修改MachO文件,在执行的时候加载咱们的framework。HYHook是我刚刚建立的framework的名称,须要修改为大家本身建立的framework名称。app

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

注意:若是只是重签名上面那行命令不要添加,在代码注入的时候再添加,而且framework名字要与建立的framework相同。编辑器

注入代码

目前咱们已经将本身建立的framework添加到了Frameworks文件夹里面,而且如今MachO文件在执行的时候也会将这个framework加载到内存中。那么接下来咱们就要开始让framework作点事情了。
一、建立一个类,名字随便,我这里叫HYHookLogin,由于后面要使用这个类去获取微信的登陆密码。
二、在.m中实现这个类的load方法,代码以下:ide

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

三、运行代码,能够看到这句话已经打印出来了,说明咱们注入的代码执行了。

注入成功

获取微信登陆密码

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

黑魔法 Method Swizzling

相信不少童鞋在这以前应该都多多少少了解过iOS的黑魔法Method Swizzling,不了解也不要紧,如今就来说讲这个黑魔法。
讲以前我默认你知道SEL和IMP对于方法来讲所表示的意义。再说一下吧

  • SEL:至关于一本书的目录,我能够经过这个目录找到IMP。
  • IMP:至关于真正要找到,要使用的东西。

简而言之,经过SEL能够找到方法实现的地址,而IMP就是方法的实现地址。
他俩的关系是一一对应的,盗一张图😁

SEL---IMP

而咱们的黑魔法看着他们一一对应很不爽,因而就有了下面这样👌

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

动态调试

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

  1. 运行项目,此时xcode会将重签名的微信安装到手机并打开。
  2. 点击登陆进入登陆页面,点击用微信号/QQ号/邮箱登陆,此时咱们就会进入到帐号密码输入界面。
  3. 在xcode上点击Debug View Hierarchy,不知道哪个?看下面。
    view debug
  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多个头文件轻轻松松,同时也能快速的全局搜索。(具体用不用根据本身须要吧😄)

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

  1. 使用sublime Text打开咱们导出的Header文件夹,以下:
    头文件
  2. command + shift + f打开全局搜索,输入@interface WCAccountMainLoginViewController进行搜索。
    搜索
  3. 双击咱们搜索到的文件,跳转到指定文件内,能够发现这里面基本上有这个类中全部属性和方法的声明,咱们要验证的onNext方法也在其中。
    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%确定了。
    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));
// /*
// 方法替换,参数说明
// 一、将要被替换的方法在哪一个类
// 二、将要被替换的方法在类中的SEL
// 三、替换方法的具体实现
// 四、方法标识,返回值类型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进行代码注入,而且在此基础上一步步逆向分析出微信的登陆密码如何窃取。之因此用窃取这个词,就是由于在用户层上,并无改变微信本来的登陆请求,只是在登陆以前添加了一点点东西用来窃取用户输入的密码。

用红色文字提示用户: 没事千万别把手机越狱,使用别人开发的插件,极可能别人的插件就有这个获取密码的功能,而后经过网络请求将你的密码上传到某个服务器上,讽刺的是这个帐号密码仍是你本身输入给人家的😂😂😂

相关文章
相关标签/搜索