https通信过程

1
2
3
4
5
6
7
服务器返回证书公钥

客户端拿着公钥去加密一段随机的字符串(对称加密的密钥)

服务器拿着私钥去解密,得到这段字符串,也就是对称加密的密钥

服务器和客户端通过对称密钥加密的密文进行通信

抓包的时候,公钥肯定和真实的服务端证书不一样

所谓hook抓包,就是把app发送数据包时调用的函数给hook下来,然后获取发送的数据包,而不是去替换证书

常见的抓包检测

不走代理

开发的时候可以在源码里指定不走系统代理

1
2
3
4
5
6
7
HttpsURLConnection设置不走代理
okhttp3设置不走代理

解决方案:
Hook设置代理的函数,手动给设置代理
在模拟器中可以使用httpv7来抓包
使用VPN抓包(httpcanary或者charles+postman就属于这种,使用这两种方法的时候不用管设置走不走代理)

代理检测与VPN检测

代理检测,通过System.getProperty来获取当前代理设置,C代码也可以实现,但是httpcanary或者charles+postman可以无视代理检测

VPN检测,见后面

单向验证与双向验证

属于证书检测的范围,单项检测就是客户端校验服务器的证书

1
2
3
4
5
6
7
8
9
10
2.1 抓包解密SSL流量,需要伪造证书
2.2 通常使用抓包工具的根证书,来签发一个服务器实体证书,这时可以证书检
2.3 客户端校验服务器证书
通常利用系统相关函数来校验证书,这时可以通过Hook相关系统函数来绕过
X509TrustManager
HostnameVerifier
okhttp3证书锁定
okhttp3证书校验
2.4 服务器校验客户端证书
有的客户端请求的时候会带上自己的内置证书一起发给服务端,就需要把内置证书找出来,放到抓包工具里

Hook抓包

Hook方式抓包,不走代理、没有VPN、不改证书,上面的检测一般不用管,但是hook也可以被检测到,也就是检测hook框架

抓到的可能没有抓包工具全

代理检测与VPN检测的实现

代理检测,通过System.getProperty来获取当前代理设置

检测VPN的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void getNetworkName() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
int count = 0;
while (networkInterfaces.hasMoreElements()) {
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();
}
}

其实就是Hook NetworkInterface这个类的各种方法,也就是java.net.NetworkInterface.getName

可以使用objection把java.net下的类全部hook,来查看app使用了哪些方法

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
// VPN的网卡号一般是tun0或者ppp0,hook的时候返回别的网卡号
if (name == "tun0" || name == "ppp0") {
return "rmnet_data0";
} else {
return name;
}
}
})
}

或者通过connectivityManager.getNetworkCapabilities()来检测VPN

1
2
3
4
5
6
7
8
9
10
11
public void networkCheck() {
try {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
Network network = connectivityManager.getActiveNetwork();

NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
Log.i("TAG", "networkCapabilities -> " + networkCapabilities.toString());
} catch (Exception e) {
e.printStackTrace();
}
}

hook也就是hook这些函数,底层还调用了一些,也没细看了,差不多就这个思路,返回正常的字符串就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java.perform(function () {
var NetworkCapabilities = Java.use("android.net.NetworkCapabilities");
NetworkCapabilities.hasTransport.implementation = function () {
return false;
}

// NetworkCapabilities.transportNameOf.implementation = function () {
// return "wifi";
// }

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的使用

Android开发里一般把网络请求放到子线程里,因为比较耗时

1
2
3
4
5
6
new Thread(){
public void run(){
String str = SSLHelper.getHttpsURLConnection("POST","https://www.baidu.com/","user");
Log.d("DemoJohn","DemoJohn:" + str);
}
}.start();

SSLHelper类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.DemoJohn.https;

import ...;

public class SSLHelper {
public static String certificate = "-----BEGIN CERTIFICATE-----\n" +
"MIIKLjCCCRagAwIBAgIMclh4Nm6fVugdQYhIMA0GCSqGSIb3DQEBCwUAMGYxCzAJ\n" +
"rlylLTTYmlW3WETOATi70HYsZN6NACuZ4t1hEO3AsF7lqjdA2HwTN10FX2HuaUvf\n" +
"5OzP+PKupV9VKw8x8mQKU6vr\n" +
"-----END CERTIFICATE-----";

private static SSLContext getSSLContext() {
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("DemoJohn","authType: " + authType);
X509Certificate cf = chain[0];

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

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

cf.checkValidity();
Log.d("DemoJohn", "IssuerDN: " + cf.getIssuerDN().toString());
Log.d("DemoJohn", "SubjectDN: " + cf.getSubjectDN().toString());
Log.d("DemoJohn", "证书版本: "+ cf.getVersion());

final boolean expected = realPubKey.equalsIgnoreCase(encoded);
if (!expected) {
throw new CertificateException("checkServerTrusted: got error public key: " + encoded);
}
Log.d("DemoJohn","证书公钥验证正确");

}

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

}

@ Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};

SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}

public static String getHttpsURLConnection(String method, String url, String outputStr) {
HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
@ Override
public boolean verify(String hostname, SSLSession session) {
Log.d("DemoJohn", hostname);
return true;
}
};

Log.d("DemoJohn", "https: " + System.getProperty("https.proxyHost"));
Log.d("DemoJohn", "https: " + System.getProperty("https.proxyPort"));
Log.d("DemoJohn", "http: " + System.getProperty("http.proxyHost"));
Log.d("DemoJohn", "http: " + System.getProperty("http.proxyPort"));

//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);
conn.setSSLSocketFactory(sslContext.getSocketFactory());
conn.setHostnameVerifier(DO_NOT_VERIFY);

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

hook的话,就hook相关的类就行,注意HttpsURLConnection的类比较杂,涉及到javax.net.ssl.HttpsURLConnectionjava.net.URLConnectioncom.android.okhttp.internal.huc.HttpsURLConnectionImpl