一
前置知识
二
核心知识点
三
SSLPinning环境下如何抓包
上文可以了解到从 HTTP 到 HTTPS 数据在传输过程中添加了一层 加密(SSL/TLS),让我们数据流量处于加密状态,不再是明文可见。一旦 app 校验了证书的指纹信息。我们的证书不再受信任了。自然而然就无法建立连接,所以必须想办法让 app 信任,才能继续抓包。当然这个分为两种情况:
上篇文件提到了一个证书包含了很多信息,那么客户端校验的原理就是: 在APP中预先设置好证书的信息,在证书校验阶段时与服务器返回的证书信息进行比较。
公钥校验
证书校验
Host校验
private void doRequest(){
new Thread(){
@Override
public void run() {
final String CA_PUBLIC_KEY = "sha256/kO7OP94daK9P8+X52s00RvJLU0SiCXA9KAg9PelfwIw=";
final String CA_DOMAIN = "www.52pojie.cn";
//校验公钥
CertificatePinner buildPinner = new CertificatePinner.Builder()
.add(CA_DOMAIN, CA_PUBLIC_KEY)
.build();
OkHttpClient client = new OkHttpClient.Builder().certificatePinner(buildPinner).build();
Request req = new Request.Builder().url("https://www.52pojie.cn/forum.php")
.build();
Call call = client.newCall(req);
try {
Response res = call.execute();
Log.e("请求成功", "状态码:" + res.code());
} catch (IOException e) {
e.printStackTrace();
Log.e("请求失败", "异常" + e);
}
}
}.start();
}
CertificatePinner buildPinner = new CertificatePinner.Builder()
.add(CA_DOMAIN, CA_PUBLIC_KEY)
.build();
//将buildPinner 传给OkHttpclient
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(buildPinner)
.build();
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(hostname);
if (pins.isEmpty()) return;
if (certificateChainCleaner != null) {
peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
}
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
// Lazily compute the hashes for each certificate.
ByteString sha1 = null;
ByteString sha256 = null;
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
} else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
} else {
throw new AssertionError("unsupported hashAlgorithm: " + pin.hashAlgorithm);
}
}
}
// Bypass OkHTTPv3 {1}
var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
console.log('[+] Bypassing OkHTTPv3 {1}: ' + a);
return;
private void doRequest2(){
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//服务器返回的证书
X509Certificate cf = chain[0];
//转换为RSA的公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) cf.getPublicKey();
//Base64 encode
String ServerPubkey = Base64.encodeToString(rsaPublicKey.getEncoded(), 0);
Log.e("服务器端返回的证书",ServerPubkey);
//读取客户端资源目录中的证书
InputStream client_input = getResources().openRawResource(R.raw.pojie);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate realCertificate = (X509Certificate) certificateFactory.generateCertificate(client_input);
String realPubkey = Base64.encodeToString(realCertificate.getPublicKey().getEncoded(), 0);
Log.e("客户端资源目录中的证书",realPubkey);
cf.checkValidity();
final boolean expected = realPubkey.equalsIgnoreCase(ServerPubkey);
Log.e("eq = ",String.valueOf(expected));
if (!expected){
throw new CertificateException("证书不一致");
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLSocketFactory factory = null;
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null,new TrustManager[]{trustManager},new SecureRandom());
factory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
SSLSocketFactory finalFactory = factory;
new Thread(){
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(finalFactory, trustManager).build();
Request req = new Request.Builder().url("https://www.52pojie.cn/forum.php").build();
Call call = client.newCall(req);
Response res = call.execute();
Log.e("请求发送成功","状态码:" + res.code());
} catch (IOException e) {
Log.e("请求发送失败","网络异常" + e);
}
}
}.start();
}
trustManager
类实现的checkServerTrusted接口。X509TrustManager trustManager = new X509TrustManager() {
...
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
...
}
....
}
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var SSLContext = Java.use('javax.net.ssl.SSLContext');
// TrustManager (Android < 7) //
////////////////////////////////
var TrustManager = Java.registerClass({
// Implement a custom TrustManager
name: 'dev.asd.test.TrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {return []; }
}
});
// Prepare the TrustManager array to pass to SSLContext.init()
var TrustManagers = [TrustManager.$new()];
// 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');
try {
// Override the init method, specifying the custom TrustManager
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
console.log('[+] Bypassing Trustmanager (Android < 7) pinner');
SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
};
} catch (err) {
console.log('[-] TrustManager (Android < 7) pinner not found');
//console.log(err);
}
private void doRequest3(){
HostnameVerifier verifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if ("www.52pojie.cn".equalsIgnoreCase(hostname)){
return true;
}
return false;
}
};
new Thread() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().hostnameVerifier(verifier).build();
Request req = new Request.Builder().url("https://www.52pojie.cn/forum.php").build();
Call call = client.newCall(req);
Response res = call.execute();
Log.e("请求发送成功", "状态码:" + res.code());
} catch (IOException ex) {
Log.e("Main", "网络请求异常" + ex);
}
}
}.start();
}
在客户端放入证书(p12/bks),客户端向服务端发送请求时,携带证书信息,在服务端会校验客户端携带过来的证书合法性
private void doRequest4(){
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
HostnameVerifier verify = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
new Thread(){
@Override
public void run() {
try {
InputStream client_input = getResources().openRawResource(R.raw.client);
Log.e("x",client_input.getClass().toString());
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(client_input, "demoli666".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "demoli666".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{trustManager}, new SecureRandom());
SSLSocketFactory factory = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(factory, trustManager).hostnameVerifier(verify).build();
Request req = new Request.Builder().url("https://xxx.xxx.xxx.xxx:443/index").build();
Call call = client.newCall(req);
Response res = call.execute();
Log.e("请求发送成功","状态码:" + res.code());
} catch (Exception e) {
Log.e("请求发送失败","网络异常" + e);
}
}
}.start();
}
Java.perform(function () {
function uuid(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
function storeP12(pri, p7, p12Path, p12Password) {
var X509Certificate = Java.use("java.security.cert.X509Certificate")
var p7X509 = Java.cast(p7, X509Certificate);
var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
ks.load(null, null);
ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
try {
var out = Java.use("java.io.FileOutputStream").$new(p12Path);
ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
} catch (exp) {
console.log(exp)
}
}
//在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
var result = this.getPrivateKey()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
return result;
}
Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
var result = this.getCertificateChain()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
return result;
}
});
四
混淆代码
java.security.KeyStore
不会被混淆,但是第三方的包都会被混淆为a.b.c.v
类似的形式。Java.use('okhttp3.CertificatePinner');
Java.use('com.square.okhttp.internal.tls.OkHostnamaVerifier');
Java.use("java.security.KeyStore");
java.security.KeyStore
没有作用。X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
}
HostnameVerifier verify = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
CertificatePinner buildPinner = new CertificatePinner.Builder()
.add(CA_DOMAIN, CA_PUBLIC_KEY)
.build();
OkHttpClient client = new OkHttpClient.Builder().certificatePinner(buildPinner).build();
okhttp3.internal.connection.RealConnection
类中的connectTls
方法:Java.perform(function () {
var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
console.log("参数:", a, b);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return this.doHandshake(a, b);
};
});// frida -UF -l 1.hook_check.js
Java.perform(function () {
var Platform = Java.use('com.android.org.conscrypt.Platform');
Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
console.log('\n[+] checkServer ',x509tm,JSON.stringify(x509tm) );
// 这里会去调用客户端证书校验的方法,不执行,就是不去校验(直接通过)。
//return this.checkServerTrusted(x509tm, chain, authType, socket);
};
});
很少遇到 不写了
Java.perform(function () {
function getFieldValue(obj, fieldName) {
var cls = obj.getClass();
var field = cls.getDeclaredField(fieldName);
field.setAccessible(true);
var name = field.getName();
var value = field.get(obj);
return value;
}
function getMethodValue(obj, methodName) {
var res;
var cls = obj.getClass();
var methods = cls.getDeclaredMethods();
methods.forEach(function (method) {
var method_name = method.getName();
console.log(method_name, method);
if (method_name === methodName) {
method.setAccessible(true);
res = method;
return;
}
})
return res;
}
var RealConnection = Java.use('uk.c');
RealConnection.f.implementation = function (a, b, c, d) {
try {
console.log("===============");
var route = getFieldValue(this, "c");
console.log('route=', route);
var address = getFieldValue(route, 'a');
console.log('address=', address);
var hostnameVerifier = getFieldValue(address, 'hostnameVerifier');
console.log('hostnameVerifier=', hostnameVerifier);
console.log('\n[+] hostnameVerifier', hostnameVerifier);
} catch (e) {
console.log(e);
}
return this.f(a, b, c, d);
};
});
connectTls中就能找到他的类和方法被混淆后的名称,可以直接Hook
五
禁用代理的场景
六
特殊框架 flutter 环境下如何抓包
function hook_ssl_verify_result(address)
{
Interceptor.attach(address, {
onEnter: function(args) {
console.log("Disabling SSL validation")
},
onLeave: function(retval)
{
console.log("Retval: " + retval)
retval.replace(0x1);
}
});
}
function disablePinning(){
// Change the offset on the line below with the binwalk result
// If you are on 32 bit, add 1 to the offset to indicate it is a THUMB function: .add(0x1)
// Otherwise, you will get 'Error: unable to intercept function at ......; please file a bug'
// 0x393DA4 换成你找到的函数地址
var address = Module.findBaseAddress('libflutter.so').add(0x393DA4)
hook_ssl_verify_result(address);
}
setTimeout(disablePinning, 1000)
七
其他抓包技巧
看雪ID:Dem0li
https://bbs.kanxue.com/user-home-976141.htm