xposed+justTrustme使用与分析
有时候在测试APP抓取数据包,配置正常,网络也是正常的,但就是抓取不到数据包;这可能是APP做了证书验证,APP通过进行证书验证,判断当前手机是否开启了代理端口,判断当前网络环境是否是证书所允许;如果开启了代理,APP中的数据流量就不会通过代理端口,我们就抓取不到数据包了。这时我们可以使用一个框架组合来解决此问题:Xposed框架+justTrustme。
安装xposed和justTrustme:
Xposed框架https://xposed.appkg.com/是一款可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,可以基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。我们现在官网中下载xposed应用(也可以在github上面下载https://github.com/Fuzion24/JustTrustMe/releases/latest),直接安装到手机中,但有一个前提手机必须root,当然模拟器也是可以的,建议还是使用真机吧,有些APP对运行环境做了判断,在模拟器环境可能会直接闪退。
在安装好xposed框架后,打开xposed应用,他会提示“xposed框架未安装”,就如他提示所言,这里安装需要重启。
点击“确定”,开始安装。
点击“install”,他会自动下载安装,下载速度根据网速而定。
当你看到这个的时候,就是安装好了,接着就是重启手机了。
接下来就是安装可以禁止证书验证的模块,安好后就可以愉快的进行抓包了,比如说:justTrustme(https://github.com/Fuzion24/JustTrustMe/releases),这个app下载之后,直接安装就行了;这个justTrustme是没有界面的,但手机会提示xposed模块没有激活;
打开xposed中的模块,就可以按到安装好的justTrustme,只需要勾选上,然后重启手机就行了。
接下来直接使用burpsuite或者其他抓包工具正常抓包就行了,justTrustMe会将APP中所有进行证书验证的方法都Hook,绕过所有证书验证达到可以正常抓包的效果。
分析justTrustme代码执行过程及原理:
在github(https://github.com/Fuzion24/JustTrustMe/releases/latest)上面下载justTrustme的源码,解压之后来分析一下源码写的什么。
先来看看justTrustme工程的主要结构,工程不大,主要的Hook代码将近500行,但是却导入了很多个包。
先说说android逆向中有一个很重要的技术,xposed的hook技术,现在我也是菜鸟;我就先从我所知道的来分析一下;所谓hook技术,hook即“钩子”,主要的作用就是在不修改目标程序的情况下融入到程序中,再找到程序中的某个函数,再将这个函数勾住,并对函数的参数进行修改,从而让程序得出我们要想要的结果。
在编写hook程序时,主要的文件有:
2、build.gradle,这个文件是对导入的lib依赖包进行导入声明;
3、xposed_init,这个文件里面主要声明HookFileName.java的路径,让系统能够找到HookFileName.java在哪里,xposed_init文件必须在新建assests文件夹,并放在assests文件夹下面;
4、AndroidManifest.xml,这个xml文件是应用清单,主要配置当前程序是一个xposed模块,最低支持的lib版本,xposed模块描述;
5、最后就是libs文件夹里面导入第三方库的lib包“XposedBridgeApi”;
经过上面的描述我们可以从工程中的xposed_init文件中我们可以得知主要的hook代码是just.trust.me路径下面的Main.java。
那先来看看导入的包,总共导入了45个包,我对导入的所有的包进行了查询,分别有android,java,apache-httpclient,xposed;大家查看代码时可以根据以下导入包的描述来分析代码。
android |
描述 |
android.content.Context; |
有关应用程序环境的全局信息 |
android.net.http.SslError; |
该类表示一组一个或多个SSL错误和关联的SSL证书。 |
android.util.Log; |
用于发送日志输出的API。 |
android.webkit.SslErrorHandler; |
表示处理SSL错误的请求。 |
android.webkit.WebView; |
显示web页面的视图。 |
java |
描述 |
java.io.IOException; |
表示发生了某种类型的I/O异常。 |
java.net.Socket; |
实现客户端套接字(也称为“套接字”) |
java.net.UnknownHostException; |
指示无法确定主机的IP地址。 |
java.util.ArrayList; |
List 接口的大小可变数组的实现 |
java.util.List; |
提供了一个可滚动的文本项列表。 |
java.security.SecureRandom; |
加密的强随机数生成器 |
java.security.KeyStore; |
密钥和证书的存储设施 |
java.security.KeyStoreException; |
KeyStore 异常处理 |
java.security.cert.CertificateException; |
各种证书问题处理 |
java.security.cert.X509Certificate; |
此类提供了一种访问 X.509 证书所有属性的标准方式 |
java.security.KeyManagementException; |
处理密钥管理的操作的通用密钥管理异常 |
java.security.NoSuchAlgorithmException; |
当请求特定的加密算法而它在该环境中不可用时抛出此异常 |
java.security.UnrecoverableKeyException; |
如果 keystore 中的密钥无法恢复,则抛出此异常 |
javax.net.ssl.HostnameVerifier; |
主机名验证的基接口 |
javax.net.ssl.KeyManager; |
JSSE 密钥管理器的基接口 |
javax.net.ssl.SSLContext; |
表示安全套接字协议的实现,它充当用于安全套接字工厂或 SSLEngine 的工厂 |
javax.net.ssl.TrustManager; |
JSSE 信任管理器的基接口 |
javax.net.ssl.X509TrustManager; |
管理使用哪一个 X509 证书来验证远端的安全套接字 |
Apache httpclient |
描述 |
org.apache.http.conn.ClientConnectionManager; |
客户端连接的管理接口 |
org.apache.http.conn.scheme.HostNameResolver; |
主机名到IP地址解析器 |
org.apache.http.conn.scheme.PlainSocketFactory; |
用于创建未加密套接字的默认类。 |
org.apache.http.conn.scheme.SchemeRegistry; |
一组受支持的协议方案 |
org.apache.http.conn.scheme.Scheme; |
封装协议,如“http”或“https” |
org.apache.http.conn.ssl.SSLSocketFactory; |
用于TLS/SSL连接的分层套接字工厂 |
org.apache.http.impl.client.DefaultHttpClient; |
为大多数常见的使用场景预先配置HttpClient的默认实现。 |
org.apache.http.impl.conn.SingleClientConnManager; |
用于单个连接的连接管理器 |
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; |
管理一个OperatedClientConnection池,并能够为来自多个执行线程的连接请求提供服务 |
org.apache.http.params.HttpParams; |
这个接口表示HTTP协议参数的集合 |
xposed |
描述 |
de.robv.android.xposed.IXposedHookLoadPackage; |
当应用程序(“Android包”)加载时得到通知。 |
de.robv.android.xposed.XC_MethodHook; |
钩子方法的回调类。 |
de.robv.android.xposed.XC_MethodReplacement; |
XC_MethodHook的一个特殊情况,它完全替换了原来的方法。 |
de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; |
包装正在加载的应用程序的信息。 |
de.robv.android.xposed.XposedHelpers.callMethod; |
调用给定对象的实例或静态方法。 |
de.robv.android.xposed.XposedHelpers.callStaticMethod; |
调用给定类的静态方法。 |
de.robv.android.xposed.XposedHelpers.findAndHookConstructor; |
查找构造函数并将其挂钩。 |
de.robv.android.xposed.XposedHelpers.findAndHookMethod; |
查找一个方法并将其挂钩。 |
de.robv.android.xposed.XposedHelpers.getObjectField; |
返回给定对象实例中对象字段的值 |
de.robv.android.xposed.XposedHelpers.newInstance; |
创建给定类的新实例。 |
de.robv.android.xposed.XposedHelpers.setObjectField; |
设置给定对象实例中的对象字段的值。 |
de.robv.android.xposed.XposedHelpers.findClass; |
使用指定的类装入器查找类。 |
现在我们看看这个Main.java中几个有代表性的代码。
程序首先实现了一个接口类IXposedHookLoadPackage,这个是编写xposed模块必须的,接下来程序定义一个“currentPackageName”获取当前APP包名的变量,以此获取目标APP包名;使用handleLoadPackage方法获取APP的包名,并将APP包名赋值给currentPackageName变量,输出运行日志,findAndHookConstructor获取当前运行的APP的结构,并勾住它,得到HTTP默认的请求方法,并对APP中默认的HTTP请求参数进行修改。
在上面的方法中的72行调用了一个方法setObjectField(param.thisObject, "connManager", getSCCM());其中getSCCM()方法替换了APP原来的HTTP默认的请求方法;自定一个getSCCM方法来获取证书和秘钥,初始化拉一个trustStore,使用这个trustStore进一步生成TrustManager来验证,先关闭主机验证,默认允许所有主机连接,重新设置连接协议,端口,最后返回一个自定义的连接管理器。
在Main.java的91行直接获取了APP的默认请求方式,连接管理,接着获取http连接的第一个参数,赋值给params,依次设置对象域替换了原来的证书。
在上面的99行调用了一个getCCM()方法,此方法用于判断连接对象中是否有SingleClientConnManager和ThreadSafeClientConnManager这两个方法,如果有则依次返回相应的方法运行结果,如果都没有则返回null。
在Main.java的105行是hook了一个SSLSocketFactory方法;获取这个方法的参数,证书,安全随机数,主机IP;接下来创建相对应的参数并赋值,判断方法里面的证书是否存在,将证书和秘钥赋值给keymanagers,自定义重新创建信任管理,并重新设置了对象域,简单的说就是它自定义了SSLSocketFactory 方法绕过校验。
Main.java中hook SSLSocketFactory方法时调用了431行的ImSureItsLegitTrustManager,这里自定义一个接受任意证书的信任管器,可以接受所有的任意客户端和服务器的证书,以此来替换了原来的TrustManager,就达到了绕过校验。
Main.java中的158行,hook一个TrustManagerFactory,修改APP中存在的信任管理器;先找到一个TrustManagerFactory的类,获取加载类,获取里面的一个getTrustManagers方法名,判断TrustManagerImpl是否存在,如果存在,将TrustManagerImpl和类加载器赋值一个class对象,接着创建一个信任管理器接口对象,又判断如果managers大于0并且cls中的信任管理不为空就直接返回,跳出这个方法,最后设置一个自定义的信任管理器。
在158行的方法中调用了一个hasTrustManagerImpl,这里的方法就是在判断目标APP中是否有TrustManagerImpl类存在,存在返回true,不存在返回false。
在Main.java的216行,hook了一个android.webkit.WebViewClient方法类,先获取方法类android.webkit.WebViewClient,找到onReceivedSslError函数,获取webview参数,SslErrorHandler,SslError,再使用替换方法,让onReceivedSslError忽略ssl错误,继续加载页面就可以绕过ssl校验了。
在Main.java中237行,代码hook一个android.app.Application类,findAndHookMethod找到这个类,获取里面的attach方法名,和一个Context参数,接着获取第一个参数赋值给自定义的context,在将第一个参数加载类方法传递到processOkHttp函数中进行执行。
processOkHttp函数在347行开始,在使用第三方库OkHttp 中进行 SSL 证书校验情况下,根据不同的情况分4中情况依次进行了try-catch,第一个try-catch过程为加载类com.squareup.okhttp.CertificatePinner,然后找到方法com.squareup.okhttp.CertificatePinner,获取里面的check方法,及参数,接着替换check的结果返回true;这里是因为通过 CertificatePinner 进行连接的 okhttp,在连接之前,会调用其 check 方法进行证书校验,那就直接返回true就绕过了校验。
第二个try-catch中是okhttp3,思路是一样的只不过这里调用的是CertificatePinner,连接前会调用check 方法进行证书校验,索性直接返回true就可以绕过。
接着是第三个try-catch,这里先依次找到okhttp3.internal.tls.OkHostnameVerifier类,再获取类中的verify方法,返回true;这是因为Okhttp3 中如果不指定 HostnameVerifier,会直接调用的OkHostnameVerifier 里面的verify方法进行服务器主机名校验,可以直接返回true绕过校验;
最后是第四个try-catch,这里也是先依次找到okhttp3.internal.tls.OkHostnameVerifier类,再获取类中的verify方法,这里校验的是证书,也可以直接返回true,然后就绕过校验了;
在Main.java的最后一个hook方法先判断TrustManagerImpl信任管理是否存在,接着找到类com.android.org.conscrypt.TrustManagerImpl进行hook,找到里面的checkServerTrusted方法,获取证书参数,接着使用hook的替换方法,新建一个X509Certificate的ArrayList表并赋值给list,最后返回这个list。这里通过直接获取X509Certificate证书绕过证书认证,这也是通过替换了TrustManager绕过了校验。
经过分析以上几个绕过证书校验的函数,可以知道JustTrustMe 是将 APP 中所有用于校验SSL证书的方法类进行Hook,有的是替换,有的是忽略证书错误,有的是直接返回true绕过证书校验。在xposed中可不仅仅只有justTrustme可以绕过证书验证,还有SSLkiller和sslunpinning也可以绕过证书验证,有机会的话都可以下载安装使用。
最后,需要保证APP的数据传输安全,需要对APP进行加固,传输过程中对传递的数据进行加密,使用https协议进行传输。