了解了HTTPS的传输流程,再来了解一下抓包相关知识。
上文可以了解到从 HTTP 到 HTTPS 数据在传输过程中添加了一层 加密(SSL/TLS),让我们数据流量处于加密状态,不再是明文可见。一旦 app 校验了证书的指纹信息。我们的证书不再受信任了。自然而然就无法建立连接,所以必须想办法让 app 信任,才能继续抓包。当然这个分为两种情况:
上篇文件提到了一个证书包含了很多信息,那么客户端校验的原理就是:在APP中预先设置好证书的信息,在证书校验阶段时与服务器返回的证书信息进行比较。
1 公钥校验
frida 脚本片段
不用的请求框架 代码不一样 需要看情况编写
2 证书校验
校验的核心逻辑:自定义的trustManager
类实现的checkServerTrusted接口
绕过:
这个一般是自定义的类然后实现了这个trustManager的接口 ,所以不确定这个类在哪,不容易hook
但是定义好trustManager会传入以下俩地方
思路是这样:实例化一个trustManager类,然后里面什么都不写,当上面两处调用到这个类时hook这两个地方,把自己定义的空trustManager类放进去,这样就可以绕过,参考下面的frida脚本
3 host(域名)校验
背景:一个证书可能对应有很多域名都可以使用,但是开发者只想让当前的应用证书校验通过后只能往指定的域名发送请求,而不想证书校验通过后往其他可以校验证书通过的域名发送请求。(证书允许往很多域名发送请求,但是app限制只能往特定域名发送请求)
在客户端放入证书(p12/bks),客户端向服务端发送请求时,携带证书信息,在服务端会校验客户端携带过来的证书合法性
开发逻辑
标志
解决方法
只需要了解到安卓开发中,系统包是无法混淆的,例如 java.security.KeyStore
不会被混淆,但是第三方的包都会被混淆为a.b.c.v
类似的形式
所以在这样的情况下 之前的hook第三方包的脚本都是不通用的
客户端证书校验的frida脚本【不通用】
服务端证书校验的frida脚本【通用】
遇到混淆的情况下,这些hook代码或多或少会失效一些,所以我们要寻找一个像服务端证书校验的系统函数来绕过客户端证书校验。
服务端的证书校验对于我们来说已经不是问题,混淆对于java.security.KeyStore
没有作用。
客户端的证书校验,我们先来捋一下开发时的逻辑
1 调用证书校验
2 主机校验
3 pinner 公钥校验 这个校验主要是调用了CertificatePinner类中check方法
可以打调用栈或者hook查看这三个方法走的调用逻辑,结论就是都走了:okhttp3.internal.connection.RealConnection
类中的connectTls
方法:
找到第三方调用栈 注意客户端的证书校验顺序
2 hostname校验
3 公钥pinner校验
现在的很多的app都是用禁止网络代理来防止抓包。在请求的时候都使用了Proxy.NO_PROXY
解决方法:
传输层的代理进行流量转发
使用charles + Postern
postern是在传输层久把流量转发指定的中间人(代理/抓包软件)
不理解传输层什么意思? 稍后来填坑
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();
}
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();
OkHttpClient client =
new
OkHttpClient.Builder()
.certificatePinner(buildPinner)
.build();
CertificatePinner buildPinner =
new
CertificatePinner.Builder()
.add(CA_DOMAIN, CA_PUBLIC_KEY)
.build();
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);
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
;
}
else
if
(pin.hashAlgorithm.equals(
"sha1/"
)) {
if
(sha1 ==
null
) sha1 = sha1(x509Certificate);
if
(pin.hash.equals(sha1))
return
;
}
else
{
throw
new
AssertionError(
"unsupported hashAlgorithm: "
+ pin.hashAlgorithm);
}
}
}
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);
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
;
}
else
if
(pin.hashAlgorithm.equals(
"sha1/"
)) {
if
(sha1 ==
null
) sha1 = sha1(x509Certificate);
if
(pin.hash.equals(sha1))
return
;
}
else
{
throw
new
AssertionError(
"unsupported hashAlgorithm: "
+ pin.hashAlgorithm);
}
}
}
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
;
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
];
RSAPublicKey rsaPublicKey = (RSAPublicKey) cf.getPublicKey();
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();
}
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
];
RSAPublicKey rsaPublicKey = (RSAPublicKey) cf.getPublicKey();
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();
}
X509TrustManager trustManager =
new
X509TrustManager() {
...
@Override
public
void
checkServerTrusted(X509Certificate[] chain, String authType) {
...
}
....
}
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'
);
var TrustManager = Java.registerClass({
name:
'dev.asd.test.TrustManager'
,
implements
: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {
return
[]; }
}
});
var TrustManagers = [TrustManager.$
new
()];
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;'
,
'[Ljavax.net.ssl.TrustManager;'
,
'java.security.SecureRandom'
);
try
{
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'
);
}
var X509TrustManager = Java.use(
'javax.net.ssl.X509TrustManager'
);
var SSLContext = Java.use(
'javax.net.ssl.SSLContext'
);
var TrustManager = Java.registerClass({
name:
'dev.asd.test.TrustManager'
,
implements
: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {
return
[]; }
}
});
var TrustManagers = [TrustManager.$
new
()];
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;'
,
'[Ljavax.net.ssl.TrustManager;'
,
'java.security.SecureRandom'
);
try
{
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'
);
}
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();
}
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();
}
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();
}
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) {
for
(i =
0
; i < len; i++) uuid[i] = chars[
0
| Math.random() * radix];
}
else
{
var r;
uuid[
8
] = uuid[
13
] = uuid[
18
] = uuid[
23
] =
'-'
;
uuid[
14
] =
'4'
;
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)
}
}
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.perform(function () {
function uuid(len, radix) {
var chars =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
.split(
''
);
var uuid = [], i;
radix = radix || chars.length;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-7-25 15:04
被Dem0li编辑
,原因: 去掉vpn