前言:
受人所托,需要对他们的产品进行反爬测试,所以就有了以下内容。
不过,我知道,针对这方面的文章太多了,是真的多,而且好早就有了,但是目前为止,很多app的防护基本也还是用的ssl pinning检测证书。
因为,目前的app要嘛不用ssl,要嘛用就是一般的ssl,基本就是在手机上装个相关软件 的代理即可,而且这个代理基本就是fiddler,charlels,burpsuite,mitmproxy(Python环境下的)四个抓包软件自带的ssl证书,然后即可抓到ssl(https)的请求
以上这些,基本可以解决大部分的app(其实很多使用ssl的网站也是这样处理)
但是因为很多app为了防止数据被分析爬取,会做ssl pinning验证
ssl painning
SSL Pinning是一种防止中间人攻击(MITM)的技术,主要机制是在客户端发起请求–>收到服务器发来的证书进行校验,如果收到的证书不被客户端信任,就直接断开连接不继续求情。
所以在遇到对关键请求开启SSL Pinning的APP时,我们抓包就只能看到APP上提示无法连接网络或者请求失败之类的提示;而在抓包工具上面,要么就只能看到一排 CONNECT 请求,获取到证书却没有后续了,要么就是一些无关的请求,找不到想要的接口
比如如下图:
针对这种,如果是web网站,我们都知道,在本地装下抓包软件自带的ssl证书就行了,但是app的话,如此操作之后还是不行,而且app还会提示没网(比如:网络连接失败,网络有问题等等的),反正意思就是没网的意思,这种就是因为app自身做了ssl pinning验证处理,验证下当前的ssl证书是否是合法允许的,如果不是就会验证失败
其实使用ssl pinning目前已经成为了趋势,那么我们的目前对象刚好就有这个怎么办呢?
目前根据我的经验,最有效的有三个方法:
- 1.使用低版本的安卓机抓包
- 2.使用ios端手机抓包
- 3.使用frida绕过证书处理
使用低版本的安卓机抓包
因为app的话,目前主流的都是用的前后端分离开发,所以越到后期,app更新新版后,越会有不同版本的后端接口存在,而且新版接口和老版接口其实返回的数据差异性很小,并且有个关键点就是,为了兼容性,会依旧留下旧版接口,因为每个用户使用的手机不一样,安卓或者ios版本不同,系统版本也就会不同,且老款手机因为内存太小,不会更新新版的app包,种种情况下来,结果就是会留下旧版接口,而且这个旧版接口安全性比新版低很多,所以可以用低版本的饿安卓机来抓包,就正常的抓包流程即可,不出意外的话,可能还用的普通的http请求。
为什么高版本的安卓就抓不到包呢,因为高版本的(安卓7以上)开始,安卓自带了一些安全机制,本质上就是只信任系统证书,不再信任用户自己安装的证书了,我们装的ssl代理证书就是自己装的,所以就会验证不通过了
使用ios端手机抓包
这个情况真的很多,因为,苹果端的appstore管理得很严,不能加些自己独特的东西,但是加ssl是可以的,但是很多app并没有加我就不知道了,这个情况就很简单,需要一台iphone,其他都是正常抓包操作,然后安装证书,把证书信任下就行了,详细的操作就不说了,网上很多教程
使用frida绕过证书处理
这个方法就是本篇文章的重点了,这个放到后面再说
其他方法
其实也有其他的方法,这些方法并不是通用的,可能运气好可以用,运气不好就没用:
安卓模拟器
用安卓模拟器,模拟低版本安卓然后抓包
对证书信任,修改APP设置
看这个app是否是自有app,如果是自有的,谷歌有debug模式,该模式下让app默认可以信任用户域的证书(trust-anchors),如果是非自有,用xposed+JustTrustMe即可,但是使用Xposed框架需要root,网上那些微信魔改小功能,什么自动抢红包,防消息撤回之类的就是用的xposed框架改的,用JustTrustMe来信任用户安装的证书
目前市面上有VitualXposed、太极等虚拟的框架,不用root也可以操作,太极这个软件挺好的,有太极-阴(免root)和太极-阳(需要root),两个版本都可以用,但是针对有些app的话,太极-阴没戏,只能太极-阳,但是既然我都已经root了,我就没必要整这些了。
如果是 App 的开发者或者把 apk 逆向出来了,那么可以直接通过修改 AndroidManifest.xml 文件,在 apk 里面添加证书的信任规则即可,详情可以参考 https://crifan.github.io/app_capture_package_tool_charles/website/how_capture_app/complex_https/https_ssl_pinning/,这种思路属于第一种信任证书的解决方案。
强制信任证书
其实就是将证书设置为系统证书,只需要将抓包软件的证书设置为系统区域即可。但这个前提是手机必须要 ROOT,而且需要计算证书 Hash Code 并对证书进行重命名,具体可以参考 https://crifan.github.io/app_capture_package_tool_charles/website/how_capture_app/complex_https/https_ssl_pinning, 把ssl代理证书强制的放到安卓机的/system/etc/security/cacerts/目录下,这个目录就是安卓机系统信任的目录。
httpcannary
这个是安卓端的抓包工具,网上吹得很火,根据我(我手机是安卓10)亲自操作,发现其实没有用,也不知道是不是我的姿势错误,或者我手机安卓系统版本太高了失效
VirtualApp
用这个可以免root操作,然后正常抓包,但是这个方法我没有实际操作过,网上的资料不多,自行查找
Xposed + JustTrustMe
Xposed 是一款 Android 端的 Hook 工具,利用它我们可以 Hook App 里面的关键方法的执行逻辑,绕过 HTTPS 的证书校验过程。JustTrustMe 是基于 Xposed 一个插件,它可以将 HTTPS 证书校验的部分进行 Hook,改写其中的证书校验逻辑,这种思路是属于第二种绕过 HTTPS 证书校验的解决方案。
当然基于 Xposed 的类似插件也有很多,如 SSLKiller、sslunpining 等等,可以自行搜索。
不过 Xposed 的安装必须要 ROOT,如果不想 ROOT 的话,可以使用后文介绍的 VirtualXposed。
具体可以参考 https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/。
VirtualXposed
Xposed 的使用需要 ROOT,如果不想 ROOT 的话,可以直接使用一款基于 VirtualApp 开发的 VirtualXposed 工具,它提供了一个虚拟环境,内置了 Xposed。我们只需要将想要的软件安装到 VirtualXposed 里面就能使用 Xposed 的功能了,然后配合 JustTrustMe 插件也能解决 SSL Pining 的问题,这种思路是属于第二种绕过 HTTPS 证书校验的解决方案。
特殊改写
其实本质上是对一些关键的校验方法进行了 Hook 和改写,去除了一些校验逻辑。但是对于一些代码混淆后的 App 来说,其校验 HTTPS 证书的方法名直接变了,那么 JustTrustMe 这样的插件就无法 Hook 这些方法,因此也就无效了。
强制全局代理
手机root后,使用proxy Droid 实现强制全局代理,让ssl代理证书生效,proxy Droid可以在UpToDown,ApkHere等的地方下载
VPN抓包
免root,在安卓机上安装packet capture,然后抓包,我试了下,我的手机(我手机是安卓10)没用
魔改JustTrustMe
在JustTrustMe插件上增加一个可以运行时根据实际情况调整ssl检测的功能,对hook增加动态适配,这个方法我没试过,我在看雪论坛里找到一个 JustTrustMePlus,点我下载
反编译app包
用apktools修改配置文件里的ssl证书检测部分,可利用jadx等工具分析源码,然后重新打包,再抓包分析,这个方法是可行的,详细的步骤自行百度吧,后续有时间的话,我单独发一篇对app的脱壳重新打包
AndServer处理
这个工具的原理就是把一个安卓机在本地作为一台服务器,然后找到数据接口,这个方法没有亲测过,更多的适用于获取app的sign/token时去获取接口
以上的方法就是我所知道的方法,各位朋友自行操作
接下来进入正题,frida hook
什么是frida
Frida是个轻量级别的hook框架, 是Python API,用JavaScript调试来逻辑
Frida的核心是用C编写的,并将Google的V8引擎注入到目标进程中,在这些进程中,JS可以完全访问内存,挂钩函数甚至调用进程内的本机函数来执行。
使用Python和JS可以使用无风险的API进行快速开发。Frida可以帮助您轻松捕获JS中的错误并为您提供异常而不是崩溃。
frida是平台原生app的Greasemonkey,说的专业一点,就是一种动态插桩工具,可以插入一些代码到原生app的内存空间去,(动态地监视和修改其行为),这些原生平台可以是Win、Mac、Linux、Android或者iOS。而且frida还是开源的。
Greasemonkey可能大家不明白,它其实就是firefox的一套插件体系,使用它编写的脚本可以直接改变firefox对网页的编排方式,实现想要的任何功能。而且这套插件还是外挂的,非常灵活机动。
frida框架主要分为两部分:
1)一部分是运行在系统上的交互工具frida CLI。
2)另一部分是运行在目标机器上的代码注入工具 frida-server
注:以下相关操作,终端里凡是 C:\Users\Administrator 开头的都是在pc机上操作的,需要在安卓机目录里操作的我都有说明,不要搞混了
环境准备
安装frida
没有python的安装python,然后安装frida:
pip install frida
pip install frida-tools
安装过程很慢,这个只能耐心等待,然后如果你是macbook的话,如果你遇到安装出错,可以看看我这篇文章的解决方法 macos 安装frida的坑
然后frida是mac,linux,windows都可以安装使用的,这个根据你自己的条件选择了
安装adb
这个就很简单,去 安卓开发网 然后下载这个工具:
如果你下载太慢可以在我这里下载:点我
下载完毕后,解压,然后放到你想放的路径,然后配置下环境变量即可,此电脑(我的电脑)- 属性-高级系统设置-环境变量-系统变量的path,新增即可
然后,打开终端:
敲adb,回车,如果有以下提示,说明你adb安装成功
以上配置是windows平台,如果是其他平台的话,自行查找,这里就不展示了
找一个安卓机(已root)
根据现在的行情,要找到一个已root的手机,问题不大也不小,但是很多时候没有必要,所以我这里就选择用安卓模拟器来辅助操作了
安装夜神模拟器,夜神默认是安卓5,你可以自行选择安卓版本,在夜神里设置已root即可
打开开发者选项里的USB调试
设置里面,关于本机,然后狂点系统版本号,开启开发者模式:
返回,会多一个开发者选项:
打开调试
adb连接安卓机(模拟器)
在安装了frida和adb的真机操作系统下,打开终端,用 adb connect IP 连接安卓机:
夜神的ip是127.0.0.1:62001,这里注意,如果你创建了多个安卓系统的话,那么你待连接的安卓机不一定是62001,可能是其他的,可以在安装目录里面找
进入后,找nox.vbox文件
用文本编辑器打开,搜索5555就能看到是哪个端口了,为什么必须是5555端口呢,因为5555就是模拟器挂载在我们windows真机上的端口
用 adb connect 127.0.0.1:端口 连接
我这里已经连接上了,所以提示已连接
连接之后可以用 adb devices查看已连接的机器:
安装frida-server
frida-server这个需要安装在安卓机上,但是安卓机我们都知道有很多个版本,对应架构才行,要查看当前安卓机的架构:
adb shell getprop ro.product.cpu.abi
然后去这里下载对应架构的frida-server : 点我
我这里是x86,安卓,所以选下面我选中那个下载,你的安卓机是什么你就选哪个就行了
然后下载很慢,我这里也提供了,点我下载
但是,一定注意,pip安装的frida版本一定要跟去frida官网下载的frida-server版本对应上,不然连不上
解压,然后用adb 传到安卓机上
adb push (本机的frida-sever文件所在目录) (安卓机目录)
这里提示太长了,看不出来,可以用adb shell 去那个目录下看下是否有frida-server即可:
修改frida-server的权限:
chmod 700 frida-server
下载一个frida hook 的js文件
这个文件,有好几个版本,我选用了两个版本,放到下面,你们自己选择吧
版本1:
setTimeout(function(){
Java.perform(function (){
console.log("");
console.log("[.] Cert Pinning Bypass/Re-Pinning");
var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
// Load CAs from an InputStream
console.log("[+] Loading our CA...")
cf = CertificateFactory.getInstance("X.509");
try {
var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
}
catch(err) {
console.log("[o] " + err);
}
var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);
bufferedInputStream.close();
var certInfo = Java.cast(ca, X509Certificate);
console.log("[o] Our CA Info: " + certInfo.getSubjectDN());
// Create a KeyStore containing our trusted CAs
console.log("[+] Creating a KeyStore for our CA...");
var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
console.log("[+] Our TrustManager is ready...");
console.log("[+] Hijacking SSLContext methods now...")
console.log("[-] Waiting for the app to invoke SSLContext.init()...")
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
console.log("[+] SSLContext initialized with our custom TrustManager!");
}
});
},0);
版本2:
Java.perform(function() {
var array_list = Java.use("java.util.ArrayList");
var ApiClient = Java.use(\'com.android.org.conscrypt.TrustManagerImpl\');
ApiClient.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) {
// console.log(\'Bypassing SSL Pinning\');
var k = array_list.$new();
return k;
}
}, 0);
然后你自己复制以上的任何一个版本的代码,然后在本地新建一个js文件,粘贴进去就行了
或者这个:
这里补充一个完全版:
Java.perform(function() {
/*
hook list:
1.SSLcontext
2.okhttp
3.webview
4.XUtils
5.httpclientandroidlib
6.JSSE
7.network\_security\_config (android 7.0+)
8.Apache Http client (support partly)
9.OpenSSLSocketImpl
10.TrustKit
11.Cronet
*/
// Attempts to bypass SSL pinning implementations in a number of
// ways. These include implementing a new TrustManager that will
// accept any SSL certificate, overriding OkHTTP v3 check()
// method etc.
var X509TrustManager = Java.use(\'javax.net.ssl.X509TrustManager\');
var HostnameVerifier = Java.use(\'javax.net.ssl.HostnameVerifier\');
var SSLContext = Java.use(\'javax.net.ssl.SSLContext\');
var quiet_output = false;
// Helper method to honor the quiet flag.
function quiet_send(data) {
if (quiet_output) {
return;
}
send(data)
}
// Implement a new TrustManager
// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
/*
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err: at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err: at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
*/
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var TrustManager;
try {
TrustManager = Java.registerClass({
name: \'org.wooyun.TrustManager\',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {
// var certs = [X509Certificate.$new()];
// return certs;
return [];
}
}
});
} catch (e) {
quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
}
// Prepare the TrustManagers array to pass to SSLContext.init()
var TrustManagers = [TrustManager.$new()];
try {
// Prepare a Empty SSLFactory
var TLS_SSLContext = SSLContext.getInstance("TLS");
TLS_SSLContext.init(null, TrustManagers, null);
var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
} catch (e) {
quiet_send(e.message);
}
send(\'Custom, Empty TrustManager ready\');
// Get a handle on the init() on the SSLContext class
var SSLContext_init = SSLContext.init.overload(
\'[Ljavax.net.ssl.KeyManager;\', \'[Ljavax.net.ssl.TrustManager;\', \'java.security.SecureRandom\');
// Override the init method, specifying our new TrustManager
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
quiet_send(\'Overriding SSLContext.init() with the custom TrustManager\');
SSLContext_init.call(this, null, TrustManagers, null);
};
/*** okhttp3.x unpinning ***/
// Wrap the logic in a try/catch as not all applications will have
// okhttp as part of the app.
try {
var CertificatePinner = Java.use(\'okhttp3.CertificatePinner\');
quiet_send(\'OkHTTP 3.x Found\');
CertificatePinner.check.overload(\'java.lang.String\', \'java.util.List\').implementation = function() {
quiet_send(\'OkHTTP 3.x check() called. Not throwing an exception.\');
}
} catch (err) {
// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf(\'ClassNotFoundException\') === 0) {
throw new Error(err);
}
}
// Appcelerator Titanium PinningTrustManager
// Wrap the logic in a try/catch as not all applications will have
// appcelerator as part of the app.
try {
var PinningTrustManager = Java.use(\'appcelerator.https.PinningTrustManager\');
send(\'Appcelerator Titanium Found\');
PinningTrustManager.checkServerTrusted.implementation = function() {
quiet_send(\'Appcelerator checkServerTrusted() called. Not throwing an exception.\');
}
} catch (err) {
// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf(\'ClassNotFoundException\') === 0) {
throw new Error(err);
}
}
/*** okhttp unpinning ***/
try {
var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
// do nothing
quiet_send("OkHttpClient.setCertificatePinner Called!");
return this;
};
// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
CertificatePinner.check.overload(\'java.lang.String\', \'[Ljava.security.cert.Certificate;\').implementation = function(p0, p1) {
// do nothing
quiet_send("okhttp Called! [Certificate]");
return;
};
CertificatePinner.check.overload(\'java.lang.String\', \'java.util.List\').implementation = function(p0, p1) {
// do nothing
quiet_send("okhttp Called! [List]");
return;
};
} catch (e) {
quiet_send("com.squareup.okhttp not found");
}
/*** WebView Hooks ***/
/* frameworks/base/core/java/android/webkit/WebViewClient.java */
/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
var WebViewClient = Java.use("android.webkit.WebViewClient");
WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
quiet_send("WebViewClient onReceivedSslError invoke");
//执行proceed方法
sslErrorHandler.proceed();
return;
};
WebViewClient.onReceivedError.overload(\'android.webkit.WebView\', \'int\', \'java.lang.String\', \'java.lang.String\').implementation = function(a, b, c, d) {
quiet_send("WebViewClient onReceivedError invoked");
return;
};
WebViewClient.onReceivedError.overload(\'android.webkit.WebView\', \'android.webkit.WebResourceRequest\', \'android.webkit.WebResourceError\').implementation = function() {
quiet_send("WebViewClient onReceivedError invoked");
return;
};
/*** JSSE Hooks ***/
/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
/* public final TrustManager[] getTrustManager() */
/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error */
// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
// TrustManagerFactory.getTrustManagers.implementation = function(){
// quiet_send("TrustManagerFactory getTrustManagers invoked");
// return TrustManagers;
// }
var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setDefaultHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function(hostnameVerifier) {
quiet_send("HttpsURLConnection.setDefaultHostnameVerifier invoked");
return null;
};
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setSSLSocketFactory(SSLSocketFactory) */
HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
return null;
};
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
return null;
};
/*** Xutils3.x hooks ***/
//Implement a new HostnameVerifier
var TrustHostnameVerifier;
try {
TrustHostnameVerifier = Java.registerClass({
name: \'org.wooyun.TrustHostnameVerifier\',
implements: [HostnameVerifier],
method: {
verify: function(hostname, session) {
return true;
}
}
});
} catch (e) {
//java.lang.ClassNotFoundException: Didn\'t find class "org.wooyun.TrustHostnameVerifier"
quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
}
try {
var RequestParams = Java.use(\'org.xutils.http.RequestParams\');
RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
sslSocketFactory = EmptySSLFactory;
return null;
}
RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
hostnameVerifier = TrustHostnameVerifier.$new();
return null;
}
} catch (e) {
quiet_send("Xutils hooks not Found");
}
/*** httpclientandroidlib Hooks ***/
try {
var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
AbstractVerifier.verify.overload(\'java.lang.String\', \'[Ljava.lang.String\', \'[Ljava.lang.String\', \'boolean\').implementation = function() {
quiet_send("httpclientandroidlib Hooks");
return null;
}
} catch (e) {
quiet_send("httpclientandroidlib Hooks not found");
}
/***
android 7.0+ network_security_config TrustManagerImpl hook
apache httpclient partly
***/
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
// try {
// var Arrays = Java.use("java.util.Arrays");
// //apache http client pinning maybe baypass
// //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
// TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
// quiet_send("TrustManagerImpl checkTrusted called");
// //Generics currently result in java.lang.Object
// return Arrays.asList(chain);
// }
//
// } catch (e) {
// quiet_send("TrustManagerImpl checkTrusted nout found");
// }
try {
// Android 7+ TrustManagerImpl
TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
quiet_send("TrustManagerImpl verifyChain called");
// Skip all the logic and just return the chain again :P
//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
return untrustedChain;
}
} catch (e) {
quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
}
// OpenSSLSocketImpl
try {
var OpenSSLSocketImpl = Java.use(\'com.android.org.conscrypt.OpenSSLSocketImpl\');
OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
quiet_send(\'OpenSSLSocketImpl.verifyCertificateChain\');
}
quiet_send(\'OpenSSLSocketImpl pinning\')
} catch (err) {
quiet_send(\'OpenSSLSocketImpl pinner not found\');
}
// Trustkit
try {
var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
Activity.verify.overload(\'java.lang.String\', \'javax.net.ssl.SSLSession\').implementation = function(str) {
quiet_send(\'Trustkit.verify1: \' + str);
return true;
};
Activity.verify.overload(\'java.lang.String\', \'java.security.cert.X509Certificate\').implementation = function(str) {
quiet_send(\'Trustkit.verify2: \' + str);
return true;
};
quiet_send(\'Trustkit pinning\')
} catch (err) {
quiet_send(\'Trustkit pinner not found\')
}
try {
//cronet pinner hook
//weibo don\'t invoke
var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");
//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {
//weibo not invoke
console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);
//true to enable the bypass, false to disable.
var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
return ret;
};
netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
console.log("cronet addPublicKeyPins hostName = " + hostName);
//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
//this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
return this;
};
} catch (err) {
console.log(\'[-] Cronet pinner not found\')
}
});
上面这个完全版本包含了如下功能,如果你想一步到位的话,就可以用这个完全版
- SSLcontext(ART only)
- okhttp
- webview
- XUtils(ART only)
- httpclientandroidlib
- JSSE
- network_security_config (android 7.0+)
- Apache Http client (support partly)
- OpenSSLSocketImpl
- TrustKit
- Cronet
任意一个都可以,不要三个都用,都用也没用,根据实际情况选用
安卓机配置代理
配置代理到开启了抓包工具的IP上:
长按wiredssid
补充一句,当配置完代理后,pc端电脑上一定要打开对应的抓包软件,不然安卓机会没网
安卓机上安装ssl证书
根据你选用的抓包工具,fiddler,charles,burpsuite,安装证书即可,你可以访问局域网下带的ip来下载,然后安装:
配置了代理再执行此步骤,不然打不开下载证书的局域网址
也可以用adb 像传frida-server一样,用adb push把证书push到安卓机上,然后在安卓机的设置-安全里本地导入证书:
用adb push 之后,还是把代理配置上,不然后面操作也无法继续,不管怎么操作,反正必须要ssl证书安装上即可
开始hook
hook的本质意思就是钩子,在开发里面通俗的说就是可以在任意流程里插一手,然后做些手脚,比如打开一个app,在启动到完全打开app,显示app的首页,这个过程就可以hook一下,比如把本来要打开首页的,改成打开第二页数据,当然这只是举个例子
启动frida-server:
/data/local/tmp/frida-server
补充下,有的高级点的app会检测本地是否启动了frida-server的程序,以及监听是否开启了27042端口,所以,如果有反调试的话,建议将frida-server改个自定义的名字,比如fsx86之类的,反正就是别让app检测到,然后启动:
/data/local/tmp/fsx86 -l 0.0.0.0:6666 (6666就是自定义端口)
这里要用绝对路径来启动,我也不知道为啥,启动,如下,warning是个警告,无所谓,说明启动成功了,只要没报错就行了
映射端口
在pc端电脑,装adb的机器上使用如下命令映射端口
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
找到需要hook的app包名
这个包名不是app的名字,是安装之后存在目录里的文件夹名,一般是com.xxxx.xxx之类的,但是有少部分奇葩的报名并不是com开头
查看当前所有的包名:
frida-ps -U
注意要安卓机里先启动了firda-server,然后adb连上了安卓机,才可以调用frida命令, 如果不启动的话,运行frida这样,Failed,失败的意思
以上查看app包,显示出来太多了,你根本不知道哪个才是我们需要的包名,可以使用下面的命令查看
adb shell pm list packages:打印设备上的所有软件包
adb shell pm list packages -f:输出包和包相关联的文件
adb shell pm list packages -d:只输出禁用的包由于本机禁用没有,输出为空
adb shell pm list packages -e:只输出启用的包
adb shell pm list packages -s:只输出系统的包
adb shell pm list packages -3:只输出第三方的包
adb shell pm list packages -i:只输出包和安装信息(安装来源)
adb shell pm list packages -u:只输出包和未安装包信息(安装来源)
adb shell pm list packages --user <USER_ID>:根据用户id查询用户的空间的所有包,USER_ID代表当前连接设备的顺序,从零开始
如果还找不到,可以先在安卓机上启动了目标app后,再用命令查看:
adb shell "dumpsys window | grep mCurrentFocus"
hook操作
frida -U -f (app包名) -l (js目录) --no-pause
注意了,这段js是放在安装了frida和adb的电脑上,不是放在安卓机上
运行完这条命令,安卓机会自动打开目标app,
app打开界面我就不展示了
如果打开的就是我们预期的那个app,那就是对的,如果打开错了,请重新获取app包名,打开之后就可以用抓包工具进行抓包了,ssl的一样的可以抓:
上面看到的https的还是会隧道,但是紧接着就有数据出现,说明还是抓到了数据包了
ok,绕过ssl pinning成功!!!
其实如果你觉得需要做些改动的话,可以写个python脚本来调用,js代码就作为文件内容读取就行了,然后进行hook操作
最后得出的结论就是,我朋友他们的产品,其实反爬做得挺好,上面的截图也可以看到,其实还是有些数据拿不到的
补充:
如果你用的模拟器在安装了app之后打不开,说明app有检测是否是模拟器或者对安卓版本做了检测,版本太低直接不给使用,那么你就只能用真机操作了,adb连接真机操作区别不大,详细的自行百度
检测模拟器的办法:
- 1.检测模拟器上特有的文件
- 2.检测qemu pipes驱动程序
- 3.检测手机号是否是155552155开头的
- 4.检测设备ID是否是15个0
- 5.检测IMSI ID是否是31026+10个0
- 6.检测运营商是否是“Android”
- 7.代码里用getInstance()方法调用任意一个方法,返回true就是模拟器
- 8.检测IMEI或者入网许可证
以上都是我以前搜集的数据,但是,根据现在的时代发展,可能模拟器也早就更新迭代了,把一些特征给抹除或者改的跟真机一样了,所以有些方法并不是有用了,这个就只有自行选择了
免root使用frida
其实不root也可以使用frida,这里我就不展开了,给一个大神写的链接,里面还有其他方法的hook,感兴趣自己看吧,点我
针对很安全性很强的app——逆向
JEB
JEB 是一款适用于 Android 应用程序和本机机器代码的反汇编器和反编译器软件。利用它我们可以直接将安卓的 apk 反编译得到 Smali 代码、jar 文件,获取到 Java 代码。有了 Java 代码,我们就能分析其中的加密逻辑了。
JEB:https://www.pnfsoftware.com/
JADX
与 JEB 类似,JADX 也是一款安卓反编译软件,可以将 apk 反编译得到 jar 文件,得到 Java 代码,从而进一步分析逻辑。
JADX:https://github.com/skylot/jadx
dex2jar、jd-gui
这两者通常会配合使用来进行反编译,同样也可以实现 apk 文件的反编译,但其用起来个人感觉不如 JEB、JADX 方便。
脱壳
一些 apk 可能进行了加固处理,所以在反编译之前需要进行脱壳处理。一般来说可以先借助于一些查壳工具查壳,如果有壳的话可以借助于 Dumpdex、FRIDA-DEXDump 等工具进行脱壳。
FRIDA-DEXDump:https://github.com/hluwa/FRIDA-DEXDump
Dumpdex:https://github.com/WrBug/dumpDex
反汇编
一些 apk 里面的加密可能直接写入 so 格式的动态链接库里面,要想破解其中的逻辑,就需要用到反汇编的一些知识了,这里可以借助于 IDA 这个软件来进行分析。
IDA:https://www.hex-rays.com/
以上的一些逆向操作需要较深的功底和安全知识,在很多情况下,如果逆向成功了,一些加密算法还是能够被找出来的,找出来了加密逻辑之后,我们用程序模拟就方便了。
模拟
逆向对于多数有保护 App 是有一定作用的,但有的时候 App 还增加了风控检测,一旦 App 检测到运行环境或访问频率等信息出现异常,那么 App 或服务器就可能产生防护,直接停止执行或者服务器返回假数据等都是有可能的。
对于这种情形,有时候我们就需要回归本源,真实模拟一些 App 的手工操作了。
adb
最常规的 adb 命令可以实现一些手机自动化操作,但功能有限。
触动精灵、按键精灵
有很多商家提供了手机 App 的一些自动化脚本和驱动,如触动精灵、按键精灵等,利用它们的一些服务我们可以自动化地完成一些 App 的操作。
触动精灵:https://www.touchsprite.com/
Appium
类似 Selenium,Appium 是手机上的一款移动端的自动化测试工具,也能做到可见即可爬的操作。
Appium:http://appium.io/
AirTest
同样是一款移动端的自动化测试工具,是网易公司开发的,相比 Appium 来说使用更方便。
AirTest:http://airtest.netease.com/
Appium/AirTest + mitmdump
mitmdump 其实是一款抓包软件,与 mitmproxy 是一套工具。这款软件配合自动化的一些操作就可以用 Python 实现实时抓包处理了。
mitmdump:https://mitmproxy.readthedocs.io/