Frida应用基础及APP https证书验证破解

安全 应用安全
Frida是适用于开发人员、逆向工程师和安全研究人员的轻量级Hook工具,它允许将JavaScript代码或库注入Windows、macOS、GNU / Linux、iOS、Android和QNX上的本机应用程序。

一、Frida简介

1. 基础介绍

Frida是适用于开发人员、逆向工程师和安全研究人员的轻量级Hook工具,它允许将JavaScript代码或库注入Windows、macOS、GNU / Linux、iOS、Android和QNX上的本机应用程序。此外, Frida还提供了一些基于Frida API构建的简单工具。

[[266929]]

Frida的四大特点:

  • 可编写脚本:将脚本注入黑盒进程,无须源代码即可劫持任何功能函数,监视加密API或跟踪私有应用程序代码;然后编辑,点击保存,即可查看结果,不需要进行编译或重新启动程序。
  • 可移植的:适用于Window、macOS、GNU / Linux、iOS、Android和QNX。从npm安装绑定的Node.js,从PyPI获取Python包,或通过其Swift绑定、.NET绑定、Qt / Qml绑定或C API使用Frida。
  • 免费的:Frida是并且将永远是免费软件(免费自由)。
  • 经过实战检验:现在安全人员正在使用Frida对大规模移动应用进行快速、深入的分析。 Frida拥有全面的测试套件,并经过多年的严格测试,涵盖了广泛的用例。

Frida访问地址:https://github.com/frida/frida。

2. 与其他Hook工具的对比

(1) Xposed的优缺点

  • 优点:在编写Java层hook插件的时候非常好用,这一点完全优越于FridaSubstrateCydia,因为它也是Android项目,可以直接编写Java代码调用各类api进行操作,而且可以安装到手机上直接使用。
  • 缺点:配置安装环境繁琐,兼容性差,在Hook底层的时候就很无助了。

(2) Frida的优缺点

  • 优点:配置环境简单,操作也很便捷,对于破解者来说开发阶段非常好用。支持Java层和Native层的hook操作,只是在Native层hook如果是非基本类型的话操作有点麻烦。
  • 缺点:因为它只适合破解者在开发阶段使用,因此它无法像Xposed那样用于实践。比如写一个微信外挂,用Frida写肯定不行,因为它无法在手机端运行。

(3) SubstrateCydia的优缺点

  • 优点:和Xposed类似可以运行在手机端。支持Java层和Native层的hook操作,但是Java层hook不怎么常用,用得比较多的是Native层的hook操作,因为它也是Android工程,可以调用系统api,操作更为方便。
  • 缺点:和Xposed一样安装配置环境烦琐,兼容性差。

以上3个工具可以说是现在用得最多的hook工具了,总结一句话就是,写Java层hook还是Xposed方便,写Native层hook适合用SubstrateCydia,而对于处在开发阶段的破解者来说,则还是Frida最靠谱。

二、Frida Hook示例

1. 连接Android设备

(1)需要一台已经root的Android设备(可以使用Android模拟器),支持4.2~6.0版本。

(2)需要使用来自Android SDK中的adb工具连接Android设备。

(3)在https://github.com/frida/frida/releases中下载相应的frida-server应用,示例使用夜神Android模拟器4.4版本,下载的frida服务端是frida-server-12.2.16-android-x86.xz。

(4)启动夜神Android模拟器4.4版本,运行adb devices命令,即可成功连接Android设备,如图2.1所示。

连接 Android 设备

图2.1连接 Android 设备

(5)运行如下命令,将frida-server复制到Android设备上,提升权限并运行。

  1. $ adb push frida-server /data/local/tmp/  
  2. $ adb shell "chmod 777 /data/local/tmp/frida-server" 
  3. $ adb shell "/data/local/tmp/frida-server &" 

(6)运行adb shell ps命令,查看frida-server已成功运行,如图2.2所示。

Frida-Server 成功运行

图2.2 Frida-Server 成功运行

(7)运行adb shell netstat命令,检查frida-server监听端口,默认为27042,如图2.3所示。

端口监听成功

图2.3 端口监听成功

(8)运行adb forward tcp:27042 tcp:27042命令,把Android设备端口转发到PC端。

(9)运行frida-ps –R命令,即可查询Android设备当前运行进程,至此连接Android设备成功(见图2.4)。另外,也可以直接通过USB将Android设备与PC端连接,运行frida-ps –U即可测试是否连接成功。

Android 设备连接成功检测

图2.4  Android 设备连接成功检测

2. HTTPS单向认证强校验Hook示例

本次示例通过在本地搭建Tomcat + SSL自签名证书环境,使用一个开源的Android HTTPS双向认证项目:

https://github.com/Frank-Zhu/AndroidHttpsDemo ,对其中部分代码进行修改后重新编译,构建本次测试的 APP应用。

(1) 基本原理

想要绕过证书锁定抓明文包就需要先知道APP是如何进行锁定操作的,然后再针对其操作进行注入解锁。

Android客户端关于证书处理的逻辑按照安全等级分类,如表2.1所示。

Android客户端证书处理的安全等级分类

表2.1 Android客户端证书处理的安全等级分类

Apache http client 因为从api23起被Android抛弃,因此使用率较低。目前更多使用的是HttpURLConnection类或第三方库OKhttp3.0进行HTTPS通信。其中OKhttp3.0的部分运用与HttpURLConnection相同,客户端都可以通过实现X509TrustManager接口的checkServerTrusted方法,将服务器证书与APP预埋证书做对比,来完成强校验。此外,也可以再通过实现HostnameVerifier接口的verify方法,校验服务器证书中的一般名称(CN)是否与域名相符。通过使用上述方法,完成客户端对服务端的证书强校验。

(2) 应用分析

由于这次是自编译的文件,就不通过APK反编译等方法查看代码了,直接看主要通信类HttpClientSslHelper的源码。

  1. public class HttpClientSslHelper { 
  2.     public static boolean isServerTrusted1 = true;  //强校验关键参数 
  3.     private static SSLContext sslContext = null
  4.     public static OkHttpClient getSslOkHttpClient(Context context) { 
  5.         OkHttpClient.Builder builder = new OkHttpClient.Builder(); 
  6.         builder.sslSocketFactory(getSslContextByCustomTrustManager(context).getSocketFactory()) 
  7.                 .hostnameVerifier(new HostnameVerifier() { 
  8.                     @Override  //实现自定义的HostnameVerifier 对象的verify校验方法 
  9.                     public boolean verify(String hostname, SSLSession session) { 
  10.                         Log.d("HttpClientSslHelper", "hostname = " + hostname); 
  11.                         if ("192.168.96.1".equals(hostname)) { 
  12.                             //根据isServerTrusted1的值进行校验,若为True,则校验成功,执行后续连接 
  13.                             return isServerTrusted1; 
  14.                         } else { 
  15.                             //由于是实验环境,if语句总为真,不会执行else语句 
  16.                             HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); 
  17.                             return hv.verify("localhost", session); 
  18.                         } 
  19.                     } 
  20.                 }); 
  21.         return builder.build(); 
  22.     } 
  23.  
  24.     public static SSLContext getSslContextByCustomTrustManager(Context context) { 
  25.         if (sslContext == null) { 
  26.             try { 
  27.                 CertificateFactory cf = CertificateFactory.getInstance("X.509","BC"); 
  28.                 InputStream caInput = new BufferedInputStream(context.getResources().getAssets().open("server.cer")); 
  29.                 final Certificate ca; 
  30.                 try { 
  31.                     ca = cf.generateCertificate(caInput); 
  32.                 } finally { 
  33.                     caInput.close(); 
  34.                 } 
  35.                 sslContext = SSLContext.getInstance("TLS"); 
  36.                 //自定义X509TrustManager接口的checkClientTrusted、checkServerTrusted和getAcceptedIssuers方法 
  37.                 sslContext.init(null, new X509TrustManager[]{new X509TrustManager() { 
  38.                     public void checkClientTrusted(X509Certificate[] chain, 
  39.                                                    String authType) throws CertificateException { 
  40.                         Log.d("HttpClientSslHelper", "checkClientTrusted --> authType = " + authType); 
  41.                         //校验客户端证书 
  42.                     } 
  43.  
  44.                     public void checkServerTrusted(X509Certificate[] chain, 
  45.                                                    String authType) throws CertificateException { 
  46.                         Log.d("HttpClientSslHelper", "checkServerTrusted --> authType = " + authType); 
  47.                         //校验服务器证书 
  48.                         for (X509Certificate cert : chain) { 
  49.                             cert.checkValidity(); 
  50.                             try { 
  51.                                  //关键点,用APP中内置的服务端证书server.cer来校验网络连接中服务器颁发的证书 
  52.                                 cert.verify(ca.getPublicKey()); 
  53.                             } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) { 
  54.                                 e.printStackTrace(); 
  55.                                 //校验失败,则更改isServerTrusted1值为false 
  56.                                 isServerTrusted1 = false
  57.                             } 
  58.                         } 
  59.                     } 
  60.  
  61.                     public X509Certificate[] getAcceptedIssuers() { 
  62.                         return new X509Certificate[0]; 
  63.                     } 
  64.                 }}, null); 
  65.             } catch (Exception e) { 
  66.                 e.printStackTrace(); 
  67.             } 
  68.         } 
  69.         return sslContext; 
  70.     } 

由上可知,将HttpClientSslHelper类的getSslContextByCustomTrustManager方法重写,重新实现checkServerTrusted方法,让其不修改isServerTrusted1的值,即可绕过证书强校验。

(3) 开始Hook

重新实现checkServerTrusted方法

构造完整脚本如下:

  1. import frida, sys 
  2.   
  3. def on_message(message, data): 
  4.     if message['type'] == 'send': 
  5.         print("[*] {0}".format(message['payload'])) 
  6.     else: 
  7.         print(message) 
  8.   
  9. jscode = ""
  10. Java.perform(function () { 
  11.     var HttpClientSslHelper = Java.use('com.frankzhu.androidhttpsdemo.HttpClientSslHelper'); 
  12.     var Log = Java.use('android.util.Log'); 
  13.     HttpClientSslHelper.getSslContextByCustomTrustManager.implementation = function () { 
  14.         var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); 
  15.         var SSLContext = Java.use('javax.net.ssl.SSLContext'); 
  16.         var TrustManager = Java.registerClass({ 
  17.             name: 'com.frankzhu.androidhttpsdemo.test', 
  18.             implements: [X509TrustManager], 
  19.             methods: { 
  20.                 checkClientTrusted: function (chain, authType) { 
  21.                 }, 
  22.                 checkServerTrusted: function (chain, authType) { 
  23.                     Log.d("Frida Hook checkServerTrusted()", "Success!!!"); 
  24.                     send("Frida Hook checkServerTrusted() Success!!!"); 
  25.                 }, 
  26.                 getAcceptedIssuers: function () { 
  27.                     return []; 
  28.                 } 
  29.             } 
  30.         }); 
  31.         // Prepare the TrustManagers array to pass to HttpClientSslHelper.sslContext.init() 
  32.         var TrustManagers = [TrustManager.$new()]; 
  33.         send("Custom, Empty TrustManager ready"); 
  34.         // Override the init method, specifying our new TrustManager 
  35.         var sslContext = SSLContext.getInstance("TLS"); 
  36.         sslContext.init(null, TrustManagers, null); 
  37.         //return的值类型必须与原来的相同,否则会出现Error: Implementation for getSslContextByCustomTrustManager expected return value compatible with 'javax.net.ssl.SSLContext',同时导致应用崩溃 
  38.         //源码里有private static SSLContext sslContext = null;如果想通过this.sslContext使用该变量,一定要注意Hook的时机,要在sslContext变为对象后再Hook,这样就不会出现应用异常崩溃 
  39.         return sslContext; 
  40.     } 
  41. }); 
  42. """ 
  43.   
  44. process = frida.get_remote_device().attach('com.frankzhu.androidhttpsdemo') 
  45. script = process.create_script(jscode) 
  46. script.on('message', on_message) 
  47. script.load() 
  48. sys.stdin.read() 

测试步骤:

  • 使用Burpsuite,设置PC端和Android端的代理;
  • 打开测试APP,运行上述Python脚本;
  • 点击APP网络测试按钮,发送网络请求(一定要在第一次点击按钮前,运行Python脚本)。

未用Frida Hook的结果如图2.5、图2.6所示。

连接报错

图2.5  连接报错

HTTPS 抓包失败

图2.6  HTTPS 抓包失败

使用Frida Hook后的结果如图2.7~图2.9所示。

脚本运行成功

图2.7  脚本运行成功

运行日志

图2.8  运行日志

HTTPS 证书验证破解成功

图2.9   HTTPS 证书验证破解成功

【本文为51CTO专栏作者“安全加”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

 

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2023-11-14 14:38:53

2012-01-09 09:40:20

App Store破解应用苹果

2009-02-09 14:17:36

2020-09-24 07:51:45

HTTPS证书接口

2017-02-16 08:53:42

2013-10-15 10:30:33

2011-08-22 14:04:52

2015-11-09 10:09:12

2020-05-20 22:37:42

HTTPSSSL双向验证

2022-07-01 09:01:49

代码hook框架

2014-04-08 11:28:52

验证码破解

2011-01-04 16:16:59

2012-05-21 10:39:52

RAID

2022-03-07 09:00:00

HTTPS证书中间件

2014-12-31 10:04:44

2016-10-24 17:47:38

2010-02-01 13:07:21

2009-10-28 10:20:10

2018-10-11 21:00:18

2013-01-17 15:51:42

Android开发应用程序组件
点赞
收藏

51CTO技术栈公众号