抓包检测的分析和绕过

抓包检测的分析和绕过

常见的抓包检测

正常抓包不走代理

HttpsURLConnection设置不走代理

okhttp3设置不走代理

代理检测与VPN检测

Hook设置代理、在模拟器中可以使用httpv7来抓包、使用VPN抓包

单向验证与双向验证

单向验证:客户端检测服务器

抓包解密SSL流量,需要伪造证书

通常使用抓包工具的根证书,来签发一个服务器实体证书,这时可以证书检测

客户端校验服务器证书:

通常利用系统相关函数来校验证书,这时可以通过Hook相关系统函数来绕过

X509TrustManager HostnameVerifier okhttp3证书锁定 okhttp3证书校验

HOOK抓包

利用HOOK函数去实现抓取系统函数来实现抓包

不用需理会各种抓包检测

抓到的可能没有抓包工具全,会被Hook检测

代理检测:

代理检测需要设置代理,函数里面通过System.setProperty来实现设置代理,而代理检测则是通过

System.getProperty()

VPN检测:

常见的VPN检测代码:

java.net.NetworkInterface.getName()

public static void getNetworkName() {

try {

Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();

int count = 0;

while (networkInterfaces.hasMoreElements()) {//使用 while 循环遍历每一个网络接口。hasMoreElements() 方法检查是否还有网络接口可以处理,nextElement() 方法返回下一个网络接口对象。

NetworkInterface next = networkInterfaces.nextElement();

logOutPut("getName获得网络设备名称=" + next.getName());

logOutPut("getDisplayName获得网络设备显示名称=" + next.getDisplayName());

logOutPut("getIndex获得网络接口的索引=" + next.getIndex());

logOutPut("isUp是否已经开启并运行=" + next.isUp());

logOutPut("isBoopback是否为回调接口=" + next.isLoopback());

logOutPut("**********************" + count++);

}

} catch (SocketException e) {

e.printStackTrace();

}

// "getName获得网络设备名称=tun0" <----- 可能出现的VPN情况 这样就是有问题的

//正常的是 ---> "getName获得网络设备名称=rmnet_data0-rmnet_data999"

这里检测的函数也就是NetworkInterface类对象的getName()方法,所以要过检测就直接去HOOK这里的函数,然后判断是否有tun的字眼,有就返回别的

function hook_vpn(){

Java.perform(function() {

var NetworkInterface = Java.use("java.net.NetworkInterface");

NetworkInterface.getName.implementation = function() {

var name = this.getName();

console.log("name: " + name);

if(name == "tun0" || name == "ppp0"){

return "rmnet_data0";

}else {

return name;

}

}

})

}

android.net.ConnectivityManager.getNetworkCapabilities

检测代码:

public void networkCheck() {

try {

ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);//获得网络连接管理器

Network network = connectivityManager.getActiveNetwork();//getActiveNetwork() 方法返回当前设备上正在使用的网络对象 Network,表示当前活动的网络连接。可能返回 null,这表示当前没有活跃网络连接。

NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);

Log.i("TAG", "networkCapabilities -> " + networkCapabilities.toString());//getNetworkCapabilities(Network network) 方法根据网络对象获取 NetworkCapabilities 实例,这个实例包含了当前网络的各种特性,比如网络类型(WiFi、移动网络等)、连接速度、传输能力等。

} catch (Exception e) {

e.printStackTrace();

}

}

//检测到VPN的字眼:networkCapabilities -> [ Transports: WIFI|VPN Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&VALIDATED&NOT_ROAMING&FOREGROUND&NOT_CONGESTED&NOT_SUSPENDED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps]

当检测到提前的网络连接时,会出现添加vpn的字眼,具体是在toString()的地方进行的添加的

public @NonNull String toString() {

final StringBuilder sb = new StringBuilder("[");

if (0 != mTransportTypes) {

sb.append(" Transports: ");

appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes,

NetworkCapabilities::transportNameOf, "|");

}

具体也就是在这里添加上的VPN的字眼的,所以我们可以直接去HOOK这里

NetworkCapabilities.appendStringRepresentationOfBitMaskToStringBuilder.implementation = function (sb, bitMask, nameFetcher, separator) {

if (bitMask == 18) {

console.log("bitMask", bitMask);

sb.append("WIFI");

}else {

console.log(sb, bitMask);

this.appendStringRepresentationOfBitMaskToStringBuilder(sb, bitMask, nameFetcher, separator);

}

}

HttpsURLConnection的GET和POST请求发送

package com.chen_chen_chen.myapplication;

import android.text.TextUtils;

import android.util.Base64;

import android.util.Log;

import java.io.BufferedReader;

import java.io.ByteArrayInputStream;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.Proxy;

import java.net.URL;

import java.security.SecureRandom;

import java.security.cert.CertificateException;

import java.security.cert.CertificateFactory;

import java.security.cert.X509Certificate;

import java.security.interfaces.RSAPublicKey;

import javax.net.ssl.HostnameVerifier;

import javax.net.ssl.HttpsURLConnection;

import javax.net.ssl.SSLContext;

import javax.net.ssl.SSLSession;

import javax.net.ssl.TrustManager;

import javax.net.ssl.X509TrustManager;

public class HttpUtil {

public static void Post()

{

new Thread(){

public void run(){

String str =getHttpsURLConnection("POST","https://baidu.com/","user");

if (str!=null) Log.d("HTTP input:", str);

else Log.d("HTTP input:", "error");

}

}.start();

}

private static SSLContext getSSLContext() {

SSLContext sslContext = null;

try {

sslContext = SSLContext.getInstance("TLS");

sslContext.init(null, null, null);

} catch (Exception e) {

e.printStackTrace();

}

return sslContext;

}

public static String getHttpsURLConnection(String method, String url, String outputStr) {

//System.setProperty("https.proxyHost", "192.168.10.1");

//System.setProperty("https.proxyPort", "8888");

//Proxy.NO_PROXY

//Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.10.1", 8888));

try {

SSLContext sslContext = getSSLContext();

if (sslContext != null) {

URL u = new URL(url);

HttpsURLConnection conn = (HttpsURLConnection) u.openConnection(Proxy.NO_PROXY);

conn.setRequestMethod("GET");

conn.setDoInput(true);//输出

conn.setUseCaches(false);

conn.setConnectTimeout(30000);

if(method.equals("POST")){

conn.setRequestMethod("POST");

conn.setDoOutput(true);//输入

conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

}

if (null != outputStr) {

OutputStream outputStream = conn.getOutputStream();

outputStream.write(outputStr.getBytes("UTF-8"));

outputStream.close();

}

conn.connect();

InputStream inputStream = conn.getInputStream();//获取输出流

InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");

BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

String str = null;

StringBuffer buffer = new StringBuffer();

while ((str = bufferedReader.readLine()) != null) {

buffer.append(str);

}

bufferedReader.close();

inputStreamReader.close();

inputStream.close();

conn.disconnect();

return buffer.toString();

}

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

}

HttpsURLConnection框架代码获取GET和POST请求

从上面得知,我们利用HttpsURLConeection申请的get和post请求的结果是通过这一部分来确定和指定的初始化数据

if (sslContext != null) {

URL u = new URL(url);

HttpsURLConnection conn = (HttpsURLConnection) u.openConnection(Proxy.NO_PROXY);

conn.setRequestMethod("GET");

conn.setDoInput(true);//输出

conn.setUseCaches(false);

conn.setConnectTimeout(30000);

if(method.equals("POST")){

conn.setRequestMethod("POST");

conn.setDoOutput(true);//输入

conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

}

if (null != outputStr) {

OutputStream outputStream = conn.getOutputStream();

outputStream.write(outputStr.getBytes("UTF-8"));

outputStream.close();

}

conn.connect();

其中主要是通过的conn对象也就是HttpsURLConnection类对象来进行初始化的,其中包含了请求方式conn.setRequestMethod("GET"),以及请求的输入,这里的输入流应该是同步的,先获取输入流对象,然后直接写入就可以写好请求包了

我们获取抓包请求和响应其实也就是通过getOutputStream()以及getInputStream()来获取的,所以其实可以去HOOK这些函数来实现我们的框架下的抓包HOOK

getOutputStream()是在com.android.okhttp.internal.huc.HttpsURLConnectionImpl

javax.net.ssl.HttpsURLConnection

java.net.URLConnection

我们可以通过objection来实现快速的定位,其实发现这个函数是在com.android.okhttp.internal.huc.HttpsURLConnectionImpl类下真正实现的

HttpsURLConnection的证书检测

首先是利用HttpsURLConnection的框架提交POST或者Get请求时初始化证书检测

SSLContext sslContext = getSSLContext();

if (sslContext != null) {

URL u = new URL(url);

HttpsURLConnection conn = (HttpsURLConnection) u.openConnection();

conn.setSSLSocketFactory(sslContext.getSocketFactory());

conn.setRequestMethod(method);

conn.setDoInput(true); // 允许输入流

conn.setUseCaches(false);

conn.setConnectTimeout(30000);

conn.setReadTimeout(30000);

conn.setSSLSocketFactory(sslContext.getSocketFactory());//这里就是初始化证书检测的地方

conn.setHostnameVerifier(DO_NOT_VERIFY);

conn.setSSLSocketFactory(sslContext.getSocketFactory()假如这里的 HttpsURLConnection的对象里面没有去单独设置证书初始化的话,那么这里就是利用的默认的证书检测,全部的证书都能过

百度证书:

-----BEGIN CERTIFICATE-----

MIIJ7DCCCNSgAwIBAgIMTkADpl62gfh/S9jrMA0GCSqGSIb3DQEBCwUAMFAxCzAJ

BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1H

bG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODAeFw0yNDA3MDgwMTQxMDJaFw0y

NTA4MDkwMTQxMDFaMIGAMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQ

MA4GA1UEBxMHYmVpamluZzE5MDcGA1UEChMwQmVpamluZyBCYWlkdSBOZXRjb20g

U2NpZW5jZSBUZWNobm9sb2d5IENvLiwgTHRkMRIwEAYDVQQDEwliYWlkdS5jb20w

ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1wFMskJ2dseOqoHptNwot

FOhdBERsZ4VQnRNKXEEXMQEfgbNtScQ+C/Z+IpRAt1EObhYlifn74kt2nTsCQLng

jfQkRVBuO/6PNGKdlCYGBeGqAL7xR+LOyHnpH9mwCBJc+WVt2zYM9I1clpXCJa+I

tsq6qpb1AGoQxRDZ2n4K8Gd61wgNCPHDHc/Lk9NPJoUBMvYWvEe5lKhHsJtWtHe4

QC3y58Vi+r5R0PWn2hyTBr9fCo58p/stDiRqp9Irtmi95YhwkNkmgwpMB8RhcGoN

h+Uw5TkPZVj4AVaoPT1ED/GMKZev0+ypmp0+nmjVg2x7yUfLUfp3X7oBdI4TS2hv

AgMBAAGjggaTMIIGjzAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADCBjgYI

KwYBBQUHAQEEgYEwfzBEBggrBgEFBQcwAoY4aHR0cDovL3NlY3VyZS5nbG9iYWxz

aWduLmNvbS9jYWNlcnQvZ3Nyc2FvdnNzbGNhMjAxOC5jcnQwNwYIKwYBBQUHMAGG

K2h0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzcnNhb3Zzc2xjYTIwMTgwVgYD

VR0gBE8wTTBBBgkrBgEEAaAyARQwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cu

Z2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wCAYGZ4EMAQICMD8GA1UdHwQ4MDYw

NKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vZ3Nyc2FvdnNzbGNhMjAx

OC5jcmwwggNhBgNVHREEggNYMIIDVIIJYmFpZHUuY29tggxiYWlmdWJhby5jb22C

DHd3dy5iYWlkdS5jboIQd3d3LmJhaWR1LmNvbS5jboIPbWN0LnkubnVvbWkuY29t

ggthcG9sbG8uYXV0b4IGZHd6LmNuggsqLmJhaWR1LmNvbYIOKi5iYWlmdWJhby5j

b22CESouYmFpZHVzdGF0aWMuY29tgg4qLmJkc3RhdGljLmNvbYILKi5iZGltZy5j

b22CDCouaGFvMTIzLmNvbYILKi5udW9taS5jb22CDSouY2h1YW5rZS5jb22CDSou

dHJ1c3Rnby5jb22CDyouYmNlLmJhaWR1LmNvbYIQKi5leXVuLmJhaWR1LmNvbYIP

Ki5tYXAuYmFpZHUuY29tgg8qLm1iZC5iYWlkdS5jb22CESouZmFueWkuYmFpZHUu

Y29tgg4qLmJhaWR1YmNlLmNvbYIMKi5taXBjZG4uY29tghAqLm5ld3MuYmFpZHUu

Y29tgg4qLmJhaWR1cGNzLmNvbYIMKi5haXBhZ2UuY29tggsqLmFpcGFnZS5jboIN

Ki5iY2Vob3N0LmNvbYIQKi5zYWZlLmJhaWR1LmNvbYIOKi5pbS5iYWlkdS5jb22C

EiouYmFpZHVjb250ZW50LmNvbYILKi5kbG5lbC5jb22CCyouZGxuZWwub3JnghIq

LmR1ZXJvcy5iYWlkdS5jb22CDiouc3UuYmFpZHUuY29tgggqLjkxLmNvbYISKi5o

YW8xMjMuYmFpZHUuY29tgg0qLmFwb2xsby5hdXRvghIqLnh1ZXNodS5iYWlkdS5j

b22CESouYmouYmFpZHViY2UuY29tghEqLmd6LmJhaWR1YmNlLmNvbYIOKi5zbWFy

dGFwcHMuY26CDSouYmR0anJjdi5jb22CDCouaGFvMjIyLmNvbYIMKi5oYW9rYW4u

Y29tgg8qLnBhZS5iYWlkdS5jb22CESoudmQuYmRzdGF0aWMuY29tghEqLmNsb3Vk

LmJhaWR1LmNvbYISY2xpY2suaG0uYmFpZHUuY29tghBsb2cuaG0uYmFpZHUuY29t

ghBjbS5wb3MuYmFpZHUuY29tghB3bi5wb3MuYmFpZHUuY29tghR1cGRhdGUucGFu

LmJhaWR1LmNvbTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0j

BBgwFoAU+O9/8s14Z6jeb48kjYjxhwMCs+swHQYDVR0OBBYEFK3KAFTK2OWUto+D

2ieAKE5ZJDsYMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCvGBoo1oyj4KmK

TJxnqwn4u7wiuq68sTijoZ3T+bYDDQAAAZCQAGzzAAAEAwBHMEUCIFwF5Jc+zyIF

Gnpxchz9fY1qzlqg/oVrs2nnuxcpBuuIAiEAu3scD6u51VOP/9aMSqR2yKHZLbHw

Fos9U7AzSdLIZa8AdgAS8U40vVNyTIQGGcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAA

AZCQAG3iAAAEAwBHMEUCIBBYQ6NP7VUDgfktWRg5QxT23QAbTqYovtV2D9O8Qc0T

AiEA2P7+44EvQ5adwL1y56oyxv/m+Gujeia7wpo7+Xbhv6MAdwAN4fIwK9MNwUBi

EgnqVS78R3R8sdfpMO8OQh60fk6qNAAAAZCQAGy+AAAEAwBIMEYCIQDU7Hxtx4c9

p9Jd+cr+DCMtyRYSc0b8cktCcbMmtDE9ygIhAIpJd4yb7jtxnaEC8oLWDushbK1v

0BIuZu6YrQvsf1nQMA0GCSqGSIb3DQEBCwUAA4IBAQCh9DfewC012/+fHZpmSpCn

y+h3/+ClAZ8cJVO+LCmYz9r6bkyhcFquJ5qUpyoW8AYtU0oUFlqH6zLIyujW+7lq

wFxB6NsXKKdwBKmMbmnZr2Fca5f+TtwD/GDJgG/egr7fI1u8194j9KEl8cK8Fujm

+UsoWklEzd1It9xkLazJR/6SwbhSR4k610pvj8rQrS4wAewuYFDaDOfqsHtDIsx1

tZfIfoB/O1wGWZQJU2M9wC8uYq0jQ2Q0MQJXuyJz04MFiGrPAS1Uk8mWd8M+3p65

Xy4iAf8uWzs1M+fcwBE8BNBghkQgE+FSUsldm+5ZBCazU0joJswzldWisXMLTagI

-----END CERTIFICATE-----

X509TrustManager trustManager = new X509TrustManager() {

@ Override

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

if (chain == null) {

throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");

}

//检测是否为空

if (!(chain.length > 0)) {

throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");

}

//检测是否长度负数

if (!(!TextUtils.isEmpty(authType) && authType.toUpperCase().contains("RSA"))) {

throw new CertificateException("checkServerTrusted: AuthType is not RSA");

}

Log.d("xiaojianbang","authType: " + authType);

X509Certificate cf = chain[0];

//获取证书

RSAPublicKey pubkey = (RSAPublicKey)cf.getPublicKey();

String encoded = Base64.encodeToString(pubkey.getEncoded(),0);

//得到的证书进行base64编码

CertificateFactory finalcf = CertificateFactory.getInstance("X.509");

X509Certificate PUB_KEY = (X509Certificate)finalcf.generateCertificate(new ByteArrayInputStream(certificate.getBytes()));

String realPubKey = Base64.encodeToString(PUB_KEY.getPublicKey().getEncoded(),0);

//这里是去获取的真实的服务器证书同时进行base64编码

cf.checkValidity();

Log.d("xiaojianbang", "IssuerDN: " + cf.getIssuerDN().toString());

Log.d("xiaojianbang", "SubjectDN: " + cf.getSubjectDN().toString());

Log.d("xiaojianbang", "证书版本: "+ cf.getVersion());

final boolean expected = realPubKey.equalsIgnoreCase(encoded);

if (!expected) {

throw new CertificateException("checkServerTrusted: got error public key: " + encoded);

}

Log.d("xiaojianbang","证书公钥验证正确");

//证书比对

}

@Override

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@ Override

public X509Certificate[] getAcceptedIssuers() {

return new X509Certificate[0];

}

};

这里是对于我们证书检测的单独设置的位置,其中包含了证书链chain的检测以及证书的获取和公钥证书的比对(细节进行了批注)

同样的conn.setHostnameVerifier(DO_NOT_VERIFY);这个函数也设置了一个检测点HostnameVerifier类下面的verify函数也要返回true才行

HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {

@ Override

public boolean verify(String hostname, SSLSession session) {

Log.d("xiaojianbang", hostname);

return true;

}

};//注意在session里面同样可以获取证书进行比对

绕过

所以我们可以直接来看看之前过HttpURLConnection的抓包检测的代码了

var HttpsURLConnection = Java.use("com.android.okhttp.internal.huc.HttpsURLConnectionImpl");

HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {

quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");

};

HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {

quiet_send("HttpsURLConnection.setHostnameVerifier invoked");

};

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;

}

//直接去HOOK的setSSLSocketFactory和setHostnameVerifier

至于这里其实是过不了检测的,因为这个走的是com.android.okhttp.internal.huc.HttpsURLConnectionImpl类下的setSslSocketFactory和setHostnameVerifier,所以过不了

okhttps提交POST和GET请求

GET请求提交的过程:

public class okHttp3Utils {

public static OkHttpClient client = new OkHttpClient.Builder().build();//直接构造一个客户端请求对象

public static void POST() {

new Thread() {

public void run() {

// OkHttpClient client = MySSLSocketFactory.createClient();

Request request = new Request.Builder()//创建请求对象

.url("https://www.baidu.com/")

.get()

.addHeader(

"User-Agent",

"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36"

)

.build();

try {

Response response = client.newCall(request).execute();//这里是提交过程中的重点:cilent.newCall()

Log.d("chen_chen_chen", "response: " + response.body().string());

} catch (IOException e) {

e.printStackTrace();

}

}

}.start();

}

}

Post请求包发送:

FormBody builder = new FormBody.Builder().add("请求键", "请求值").add("请求键1", "请求值1").build();

// OkHttpClient client = MySSLSocketFactory.createClient();

Request request = new Request.Builder()

.url("https://www.baidu.com/")

.post(builder)

也就是在这里多了这些东西

LoggingInterceptor拦截器

这个函数是加载在请求之前和响应之后的函数,和HOOK一样,都可以获取过程中的信息

class LoggingInterceptor implements Interceptor{

@Override

public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

long time1 = System.nanoTime();

Log.d("chen_chen_chen",String.format("Sending request %s on %s %n %s",request.url(),chain.connection(),request.headers()));

//请求前

Response response = chain.proceed(request);

long time2 = System.nanoTime();

Log.d("chen_chen_chen",String.format("Sending response %s on %s %n %s",response.request().url(),(time2-time1)/1e6d,response.headers()));

return response;

//响应后

}

}

/*Sending request https://www.baidu.com/ on null

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36

2024-10-31 16:52:40.388 18715-18888 chen_chen_chen com.chen_chen_chen.myapplication D Sending response https://www.baidu.com/ on 29636.590765

*/

loggingInterceptor拦截器

package com.chen_chen_chen.myapplication;

import android.util.Log;

import java.io.EOFException;

import java.io.IOException;

import java.nio.charset.Charset;

import java.util.concurrent.TimeUnit;

import okhttp3.Connection;

import okhttp3.Headers;

import okhttp3.Interceptor;

import okhttp3.MediaType;

import okhttp3.Request;

import okhttp3.RequestBody;

import okhttp3.Response;

import okhttp3.ResponseBody;

import okhttp3.internal.http.HttpHeaders;

import okio.Buffer;

import okio.BufferedSource;

import okio.GzipSource;

import okhttp3.Interceptor;

public class okhttp3Logging implements Interceptor {

private static final String TAG = "okhttpGET";

private static final Charset UTF8 = Charset.forName("UTF-8");

@Override public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

RequestBody requestBody = request.body();

boolean hasRequestBody = requestBody != null;

Connection connection = chain.connection();

String requestStartMessage = "--> "

+ request.method()

+ ' ' + request.url();

Log.e(TAG, requestStartMessage);

if (hasRequestBody) {

// Request body headers are only present when installed as a network interceptor. Force

// them to be included (when available) so there values are known.

if (requestBody.contentType() != null) {

Log.e(TAG, "Content-Type: " + requestBody.contentType());

}

if (requestBody.contentLength() != -1) {

Log.e(TAG, "Content-Length: " + requestBody.contentLength());

}

}

Headers headers = request.headers();

for (int i = 0, count = headers.size(); i < count; i++) {

String name = headers.name(i);

// Skip headers from the request body as they are explicitly logged above.

if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {

Log.e(TAG, name + ": " + headers.value(i));

}

}

if (!hasRequestBody) {

Log.e(TAG, "--> END " + request.method());

} else if (bodyHasUnknownEncoding(request.headers())) {

Log.e(TAG, "--> END " + request.method() + " (encoded body omitted)");

} else {

Buffer buffer = new Buffer();

requestBody.writeTo(buffer);

Charset charset = UTF8;

MediaType contentType = requestBody.contentType();

if (contentType != null) {

charset = contentType.charset(UTF8);

}

Log.e(TAG, "");

if (isPlaintext(buffer)) {

Log.e(TAG, buffer.readString(charset));

Log.e(TAG, "--> END " + request.method()

+ " (" + requestBody.contentLength() + "-byte body)");

} else {

Log.e(TAG, "--> END " + request.method() + " (binary "

+ requestBody.contentLength() + "-byte body omitted)");

}

}

long startNs = System.nanoTime();

Response response;

try {

response = chain.proceed(request);

} catch (Exception e) {

Log.e(TAG, "<-- HTTP FAILED: " + e);

throw e;

}

long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

ResponseBody responseBody = response.body();

long contentLength = responseBody.contentLength();

String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";

Log.e(TAG, "<-- "

+ response.code()

+ (response.message().isEmpty() ? "" : ' ' + response.message())

+ ' ' + response.request().url()

+ " (" + tookMs + "ms" + (", " + bodySize + " body:" + "") + ')');

Headers myheaders = response.headers();

for (int i = 0, count = myheaders.size(); i < count; i++) {

Log.e(TAG, myheaders.name(i) + ": " + myheaders.value(i));

}

if (!HttpHeaders.hasBody(response)) {

Log.e(TAG, "<-- END HTTP");

} else if (bodyHasUnknownEncoding(response.headers())) {

Log.e(TAG, "<-- END HTTP (encoded body omitted)");

} else {

BufferedSource source = responseBody.source();

source.request(Long.MAX_VALUE); // Buffer the entire body.

Buffer buffer = source.buffer();

Long gzippedLength = null;

if ("gzip".equalsIgnoreCase(myheaders.get("Content-Encoding"))) {

gzippedLength = buffer.size();

GzipSource gzippedResponseBody = null;

try {

gzippedResponseBody = new GzipSource(buffer.clone());

buffer = new Buffer();

buffer.writeAll(gzippedResponseBody);

} finally {

if (gzippedResponseBody != null) {

gzippedResponseBody.close();

}

}

}

Charset charset = UTF8;

MediaType contentType = responseBody.contentType();

if (contentType != null) {

charset = contentType.charset(UTF8);

}

if (!isPlaintext(buffer)) {

Log.e(TAG, "");

Log.e(TAG, "<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");

return response;

}

if (contentLength != 0) {

Log.e(TAG, "");

Log.e(TAG, buffer.clone().readString(charset));

}

if (gzippedLength != null) {

Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte, "

+ gzippedLength + "-gzipped-byte body)");

} else {

Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte body)");

}

}

return response;

}

/**

* Returns true if the body in question probably contains human readable text. Uses a small sample

* of code points to detect unicode control characters commonly used in binary file signatures.

*/

static boolean isPlaintext(Buffer buffer) {

try {

Buffer prefix = new Buffer();

long byteCount = buffer.size() < 64 ? buffer.size() : 64;

buffer.copyTo(prefix, 0, byteCount);

for (int i = 0; i < 16; i++) {

if (prefix.exhausted()) {

break;

}

int codePoint = prefix.readUtf8CodePoint();

if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {

return false;

}

}

return true;

} catch (EOFException e) {

return false; // Truncated UTF-8 sequence.

}

}

private boolean bodyHasUnknownEncoding(Headers myheader s) {

String contentEncoding = myheaders.get("Content-Encoding");

return contentEncoding != null

&& !contentEncoding.equalsIgnoreCase("identity")

&& !contentEncoding.equalsIgnoreCase("gzip");

}

}

OKhttp3

证书检测:

首先是设置证书的函数:

public static OkHttpClient client = new OkHttpClient.Builder()

.sslSocketFactory(createSSLSocketFactory(new ByteArrayInputStream(certificate.getBytes())),trustManager)

.hostnameVerifier(new TrustAllHostnameVerifier())

.certificatePinner(CPinner)

.build();

这几个OkHttpClient类下的方法都会使得证书检测到,同时这里的sslSocketFactory也就是在设置对应的证书的位置了,也是算法进行之后直接去比对证书的位置,

certificatePinner(CPinner)这里的方法其实是对于证书进行SHA1或者是SHA256的算法进行一个计算之后的证书校验,比对的是算法之后的SHA值的校验

hostnameVerifier(new TrustAllHostnameVerifier())这里通过也可以进行证书的校验,因为其获取的参数可以得到证书的值

绕过sslSocketFactory:

.sslSocketFactory(createSSLSocketFactory(new ByteArrayInputStream(certificate.getBytes())),trustManager)

这里同时是去HOOK对应类下的sslContext对象的init()这里规定了对于init的TrustManager的位置

private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) {

try {

SSLContext sslContext = Platform.get().getSSLContext();

sslContext.init(null, new TrustManager[] { trustManager }, null);

return sslContext.getSocketFactory();

} catch (GeneralSecurityException e) {

throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.

}

}

HOOK:

自定义TrustManager

TrustManager = Java.registerClass({//创建自定义的 TrustManager

name: 'org.wooyun.TrustManager',

implements: [X509TrustManager],

methods: {

checkClientTrusted: function(chain, authType) {},//checkClientTrusted 和 checkServerTrusted 方法被重写以不执行任何检查。

checkServerTrusted: function(chain, authType) {},

getAcceptedIssuers: function() {

// var certs = [X509Certificate.$new()];

// return certs;

return [];

}

}

});

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);

};

上面通过去HOOK对应的SSLContext初始化的位置,实现了对应的自写的TrustManager来接受所以的证书

绕过 .hostnameVerifier(new TrustAllHostnameVerifier())

这里的证书检测也很简单了,不过需要返回的是对应的类对象,这里我们采用了自写的类对象进行赋值

var OkHttpClient$Builder = Java.use('okhttp3.OkHttpClient$Builder');

quiet_send('OkHttpClient$Builder Found');

console.log("hostnameVerifier", OkHttpClient$Builder.hostnameVerifier);

OkHttpClient$Builder.hostnameVerifier.implementation = function () {

quiet_send('OkHttpClient$Builder hostnameVerifier() called. Not throwing an exception.');

return this;

}

var myHostnameVerifier = Java.registerClass({

name: 'com.chenchenchen.MyHostnameVerifier',

implements: [HostnameVerifier],

methods: {

verify: function (hostname, session) {

return true;

}

}

});

var OkHttpClient = Java.use('okhttp3.OkHttpClient');

OkHttpClient.hostnameVerifier.implementation = function () {

quiet_send('OkHttpClient hostnameVerifier() called. Not throwing an exception.');

return myHostnameVerifier.$new();

}

绕过 .certificatePinner(CPinner)

这里的绕过也是一样的,在CertificatePinner类下的check方法去比对了得到的公钥和证书SHA值的对比,我们HOOK这里去直接绕过

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.');

}

混淆之后的HOOK定位

在混淆之后我们要去定位这些证书校验的函数怎么去找?

首先是 .certificatePinner(CPinner):

这里是在对应check()方法内部的findMatchingPins(),会去比对的是假如的pins的是否匹配,所以我们可以去HOOK对应的系列函数来实现打印堆栈的情况,最后实现函数定位

这里的系统函数ArrayList的add()方法,去直接HOOK,打印堆栈

List findMatchingPins(String hostname) {

List result = Collections.emptyList();

for (Pin pin : pins) {

if (pin.matches(hostname)) {

if (result.isEmpty()) result = new ArrayList<>();

result.add(pin);

}

}

return result;

}

MessageDigest.digest check函数的SHA算法

在这里的SHA算法之后的证书比对之前,肯定会去调用对应的SHA算法的加密,所以

private ByteString digest(String algorithm) {

try {

return ByteString.of(MessageDigest.getInstance(algorithm).digest(data));

} catch (NoSuchAlgorithmException e) {

throw new AssertionError(e);

}

}

也可以去HOOK这里的MessageDigest.digest

网络无法访问会抛出错误,直接Hook错误比如 javax.net.ssl.SSLHandshakeException.$init

这里会报错,那么直接去HOOK对应的提示错误的地方

OKhttps3算法源码分析

okhttps3的算法源码的分析在现在貌似都被全部的Native化了,进不了Java层的函数题内部,大概的内容是Interceptor的处理,同时是对于Socket进行了connect,这里的Socket包含了java.net.Socket 包下面的socket 以com.android.org.conscrypt.Java8FileDescriptorSocket下的 sslSocket两种Socket,而且我们则是要去查看的在数据传输过程中的sslSocket的加密数据。

最终可以定位到com.android.org.conscrypt.Java8FileDescriptorSocket的getOutputStream的位置,这里是对于请求的进入的位置

最终可以定位到的位置是/src/main/java/org/conscrypt/[NativeSsl.java] 下的 write函数

406 void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)

407 throws IOException {

408 lock.readLock().lock();

409 try {

410 if (isClosed() || fd == null || !fd.valid()) {

411 throw new SocketException("Socket is closed");

412 }

413 NativeCrypto

414 .SSL_write(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);

415 } finally {

416 lock.readLock().unlock();

417 }

418 }

到了NativeCrypto类下的SSL_write函数 (这个函数被Native化了)

1088 static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd,

1089 SSLHandshakeCallbacks shc, byte[] b, int off, int len, int writeTimeoutMillis)

1090 throws IOException;

以及可能在响应包里面的read,读取服务器的响应

391 int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)

392 throws IOException {

393 lock.readLock().lock();

394 try {

395 if (isClosed() || fd == null || !fd.valid()) {

396 throw new SocketException("Socket is closed");

397 }

398 return NativeCrypto

399 .SSL_read(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);

400 } finally {

401 lock.readLock().unlock();

402 }

403 }

同时还要SSL_read函数同样Native化了

1082 static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc,

1083 byte[] b, int off, int len, int readTimeoutMillis) throws IOException;

OKHttps3自吐算法Java层

Java.perform(function () {

var ByteString = Java.use("com.android.okhttp.okio.ByteString");

function toBase64(tag, data) {

console.log(tag + " Base64: \n", ByteString.of(data).base64());

}

function toHex(tag, data) {

console.log(tag + " Hex: \n", ByteString.of(data).hex());

}

function toUtf8(tag, data) {

console.log(tag + " Utf8: \n", ByteString.of(data).utf8());

}

var NativeCrypto = Java.use("com.android.org.conscrypt.NativeCrypto");

NativeCrypto.SSL_write.implementation = function (ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis) {

console.log(offset, len);

toUtf8("chen_chen SSL_write: ", buf);

console.log("=======================================================");

this.SSL_write(ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);

}

NativeCrypto.SSL_read.implementation = function (ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis) {

console.log(offset, len);

toUtf8("chen_chen SSL_read", buf);

console.log("=======================================================");

return this.SSL_read(ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);

}

JNI层源码的分析:

Naitve化之后的函数名为对应的类名_函数比如SSL_write ——>在NaitveCrypto的类里——>名为 NaitveCrypto_SSL_write

由此来开始对应的源码分析过程:

/external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc

static void NativeCrypto_SSL_write

(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject,

8286 jobject shc, jbyteArray b, jint offset, jint len,

8287 jint write_timeout_millis)

/external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc

static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len,

8146 SslError* sslError, int write_timeout_millis)

boringssl是谷歌从openssl改过来的,ssl_lib.cc会编译到libssl.so中

/external/boringssl/src/ssl/ssl_lib.cc

int SSL_write(SSL *ssl, const void *buf, int num)

int SSL_read(SSL *ssl, void *buf, int num)

以上两个函数就是r0capture的hook点

/external/boringssl/src/ssl/s3_pkt.cc

int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *in,

130 int len)

/external/boringssl/src/ssl/s3_pkt.cc

static int do_ssl3_write(SSL *ssl, int type, const uint8_t *in, unsigned len)

在这之前,数据是明文

========================================================================================================

在这之后,数据是密文

/external/boringssl/src/ssl/s3_pkt.cc

static int ssl3_write_pending(SSL *ssl, int type, const uint8_t *in,

205 unsigned int len)

/external/boringssl/src/ssl/ssl_buffer.cc

int ssl_write_buffer_flush(SSL *ssl)

static int dtls_write_buffer_flush(SSL *ssl)

/external/boringssl/src/crypto/bio/bio.c

int BIO_write(BIO *bio, const void *in, int inl)

——————————————————————————————————————————————————————————————————————

libcrypto.so

/external/boringssl/src/crypto/bio/socket.c

static int sock_read(BIO *b, char *out, int outl) {

108 int ret = 0;

109

110 if (out == NULL) {

111 return 0;

112 }

113

114 bio_clear_socket_error();

115 #if defined(OPENSSL_WINDOWS)

116 ret = recv(b->num, out, outl, 0);

117 #else

118 ret = read(b->num, out, outl);

119 #endif

120 BIO_clear_retry_flags(b);

121 if (ret <= 0) {

122 if (bio_fd_should_retry(ret)) {

123 BIO_set_retry_read(b);

124 }

125 }

126 return ret;

127 }

128

129 static int sock_write(BIO *b, const char *in, int inl) {

130 int ret;

131

132 bio_clear_socket_error();

133 #if defined(OPENSSL_WINDOWS)

134 ret = send(b->num, in, inl, 0);

135 #else

136 ret = write(b->num, in, inl);

137 #endif

138 BIO_clear_retry_flags(b);

139 if (ret <= 0) {

140 if (bio_fd_should_retry(ret)) {

141 BIO_set_retry_write(b);

142 }

143 }

144 return ret;

145 }

最终交给libc.so中的 write函数

SSL自吐HOOK

通常情况下HOOK点:在加密之前可以HOOK

int SSL_write(SSL *ssl, const void *buf, int num)

int SSL_read(SSL *ssl, void *buf, int num)

这两个函数其实是在libssl.so的系统so库中,但是开发中其实可以把对应实现的函数copy下来,放到别的so中,这样就找不到了

这种情况下,我们只能去HOOK加密之后的函数,然后打印对应的堆栈信息,看走的是哪个函数

HOOK点:在加密之后可以HOOK

这时候就可以去更底层看libc.so中(打印堆栈)

ssize_t write(int fd, const void * buf, size_t count)ssize_t read(int fd, void * buf, size_t count)

自吐代码:

Java.perform(function () {

var ByteString = Java.use("com.android.okhttp.okio.ByteString");

function toBase64(tag, data) {

console.log(tag + " Base64: \n", ByteString.of(data).base64());

}

function toHex(tag, data) {

console.log(tag + " Hex: \n", ByteString.of(data).hex());

}

function toUtf8(tag, data) {

console.log(tag + " Utf8: \n", ByteString.of(data).utf8());

}

});

var SSL_write_addr = Module.findExportByName("libssl.so", "SSL_write");

var SSL_read_addr = Module.findExportByName("libssl.so", "SSL_read");

console.log(SSL_write_addr, SSL_read_addr);

Interceptor.attach(SSL_write_addr, {

onEnter: function (args) {

console.log("SSL_write_addr: ", Process.getCurrentThreadId() + '\n' +

Thread.backtrace(this.context, Backtracer.FUZZY)

.map(DebugSymbol.fromAddress).join('\n') + '\n');

console.log("SSL_write arg[1]"+hexdump(args[1], {length: args[2].toInt32()}));

}, onLeave: function (retval) {

}

});

Interceptor.attach(SSL_read_addr, {

onEnter: function (args) {

this.args1 = args[1];

}, onLeave: function (retval) {

var nums = retval.toInt32();

if (nums > 0) {

console.log("SSL_read_addr: ", Process.getCurrentThreadId() + '\n' +

Thread.backtrace(this.context, Backtracer.FUZZY)

.map(DebugSymbol.fromAddress).join('\n') + '\n');

console.log("SSL_read arg[1]"+hexdump(this.args1, {length: nums}));

}

}

});

var write_addr = Module.findExportByName("libc.so", "write");

var read_addr = Module.findExportByName("libc.so", "read");

console.log(write_addr, read_addr);

Interceptor.attach(write_addr, {

onEnter: function (args) {

console.log("write_addr: ", Process.getCurrentThreadId() + '\n' +

Thread.backtrace(this.context, Backtracer.FUZZY)

.map(DebugSymbol.fromAddress).join('\n') + '\n');

console.log("write arg[1]"+hexdump(args[1], {length: args[2].toInt32()}));

}, onLeave: function (retval) {

}

});

Interceptor.attach(read_addr, {

onEnter: function (args) {

this.args1 = args[1];

}, onLeave: function (retval) {

var nums = retval.toInt32();

if (nums > 0) {

console.log("read_addr: ", Process.getCurrentThreadId() + '\n' +

Thread.backtrace(this.context, Backtracer.FUZZY)

.map(DebugSymbol.fromAddress).join('\n') + '\n');

console.log("SSL_read arg[1]"+hexdump(this.args1, {length: nums}));

}

}

});

这里的int SSL_write(SSL *ssl, const void *buf, int num) 和 int SSL_read(SSL *ssl, void *buf, int num)的结果都是解密过程的

而ssize_t write(int fd, const void * buf, size_t count) 和 ssize_t read(int fd, void * buf, size_t count)都是加密过程的

单向检测和双向检测的细节处理以及绕过

单向检测是客户端校验服务器的证书

在客户端申请了SSL/TLS连接之后,服务器会发送SSL/TLS证书给客户端,这里便是客户端去校验服务器发送的证书了,这里的校验可以通过HOOK证书派发,实现全部证书的通过。

双向检测是服务器校验客户端的证书

客户端发起连接:客户端请求与服务器建立 SSL/TLS 连接。服务器发送证书服务器首先发送其 SSL/TLS 证书给客户端。客户端验证服务器证书的有效性(如前面提到的单向验证步骤);客户端发送证书:如果服务器要求,客户端也需要发送其证书给服务器进行校验,这里就是对应的服务器校验客户端了。

双向检测绕过:

对于可能出现的双向检测,假如要去绕过服务器校验客户端,则需要去获取到对应的客户端的证书,这样的证书可能会以文件的形式存放在对应的apk的压缩文件里,也能是在内存加载,我们可以通过dump下对应的内存读取。

而在证书双向验证的时候 Keystore.load()是对应证书进行加载使用的( 通常有证书密码 )我们可以去HOOK这里的代码去实现证书的获取

public final void load(InputStream stream, char[] password)

传入的是InputStream对象(证书)和证书密码

不过也可能这里的证书为null,然后在之后去设置

keyStore.setCertificateEntry(certificateAlias, certificate);

HOOK代码:

Java.perform(function () {

var KeyStore = Java.use("java.security.KeyStore");

var str = Java.use("java.lang.String");

KeyStore.load.overload("java.io.InputStream", "[C").implementation = function (input, pwdStr) {

if (input) {

console.log("pwdStr: ", str.$new(pwdStr));

var file = Java.use("java.io.File").$new("/data/data/com.xh.xinghe/xiaojianbang.p12");

// File file = new File("/data/data/com.xh.xinghe/xiaojianbang.p12");

var output = Java.use("java.io.FileOutputStream").$new(file);

//FileOutputStream output = new FileOutputStream(file)

var r, myArr = [];

for (var i = 0; i < 1024; i++) {

myArr[i] = 0;

}

var buffer = Java.array("byte", myArr);

//数组转byte array

while((r = input.read(buffer)) > 0) {

output.write(buffer, 0, r);

}

//读证书写入文件

console.log("save");

output.close();

}

return this.load(input, pwdStr);

}});

得到的证书通过cp 到 /sdcard/下然后导入抓包工具 这样就可以实现服务器校验客户端了

相关推荐

★★★★★★《梦幻西游》攻略 梦幻西游钓鱼攻略★★★★★★
滌字意思,滌的拼音,滌字发音
365bet体育足球世界

滌字意思,滌的拼音,滌字发音

📅 02-21 👁️ 1299
世界杯上的真男人
best365体育入口中文版

世界杯上的真男人

📅 01-18 👁️ 3282