抓包应该是被问到最多的事情了, 记录一些片段.
Android
pinning 校验类和方法被混淆
珍惜Any
之前提过一种思路
是遍历所有类, 按照特征去判断校验类和方法.
此处的思路则是, 先跑一遍并让它失败, 找到特征打印堆栈, 即可锁定位置, 再行 hook 即可.
先去看 okhttp
这一块的 源码:
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
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(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);
}
}
}
// If we couldn't find a matching pin, format a nice exception.
StringBuilder message = new StringBuilder()
.append("Certificate pinning failure!")
.append("\n Peer certificate chain:");
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
message.append("\n ").append(pin(x509Certificate))
.append(": ").append(x509Certificate.getSubjectDN().getName());
}
message.append("\n Pinned certificates for ").append(hostname).append(":");
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
message.append("\n ").append(pin);
}
throw new SSLPeerUnverifiedException(message.toString());
}
则去寻找 绝不会被混淆的类
来进行 hook. 锁定了几个选择:
X509Certificate.getPublicKey
/getEncoded
ByteString.sha1
StringBuilder.append
SSLPeerUnverifiedException
此处挑选第四条:
1
2
3
4
5
6
7
Java.use('javax.net.ssl.SSLPeerUnverifiedException').$init
.overload('java.lang.String')
.implementation = function (reason) {
console.log("SSLPeerUnverifiedException", reason);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
return this.$init(reason);
};
运行完堆栈信息如下:
SSLPeerUnverifiedException.$init Certificate pinning failure!
Peer certificate chain:
sha256/bQ3........
Pinned certificates for ****.com:
sha256/**************=
sha256/**************=
sha256/**************=
java.lang.Throwable
at okhttp3.CertificatePinner.a(SourceFile)
at okhttp3.internal.connection.RealConnection.a(SourceFile)
okhttp3.CertificatePinner.a
即混淆前的 okhttp3.CertificatePinner.check
1
2
3
4
5
6
7
Java.use('okhttp3.CertificatePinner')['a']
.overload('java.lang.String', 'java.util.List')
.implementation = function (hostname, peerCertificates) {
console.log(hostname);
}
或者更简单一些:
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
Java.use('java.lang.Exception')['$init']
.overload()
.implementation = function () {
console.log("[Exception.$init 1]")
return this.$init()
}
Java.use('java.lang.Exception')['$init']
.overload('java.lang.String')
.implementation = function (a) {
if ((a + '').indexOf('verification') != -1 || (a + '').indexOf('Trusted') != -1) {
console.log("[Exception.$init 2]", a)
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
}
return this.$init(a)
}
Java.use('java.lang.Exception')['$init']
.overload('java.lang.Throwable')
.implementation = function (a) {
console.log("[Exception.$init 3]")
return this.$init(a)
}
Java.use('java.lang.Exception')['$init']
.overload('java.lang.String', 'java.lang.Throwable')
.implementation = function (a, b) {
console.log("[Exception.$init 4]", a)
return this.$init(a, b)
}
hook 设置代理
遇到个奇怪的样本, 设置了
NO_PROXY
, 一般这种情况我用drony
, 但或许是实现的问题, 就会造成包体有问题, 遂考虑用 hook 代码设置代理.
先分析是什么网络框架, 发现是 okhttp3, 查看 源码
中返回 proxy 的地方, 编写代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 先构造一个 java.net.Proxy
const host = Java.use('java.lang.String').$new("192.168.0.123");
const port = 8888;
const ProxyClz = Java.use('java.net.Proxy')
const InetSocketAddressClz = Java.use('java.net.InetSocketAddress');
const addr = InetSocketAddressClz.$new.overload('java.lang.String', 'int').call(InetSocketAddressClz, host, port);
const HTTP = Java.use('java.net.Proxy$Type').class.getEnumConstants()[1]
console.log(addr);
console.log(HTTP)
const mProxy = ProxyClz.$new.overload('java.net.Proxy$Type', 'java.net.SocketAddress').call(ProxyClz, HTTP, addr);
console.log(mProxy);
Java.use('okhttp3.OkHttpClient')['proxy']
.overload()
.implementation = function () {
console.log("[OkHttpClient][proxy]");
return mProxy;
}
双向认证
感觉双向认证其实比双向绑定简单, 核心就是 dump 证书和密码.
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
console.log("Script loaded successfully ");
Java.perform(function () {
const ByteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream");
const FileOutputStream = Java.use("java.io.FileOutputStream");
const KeyStore = Java.use('java.security.KeyStore');
const store = KeyStore.store.overload('java.io.OutputStream', '[C');
java_security_KeyStore__load(function (instance, charArray) {
if (instance.getType() === "PKCS12" && !dumped) {
dumped = true;
console.log("dumping...")
try {
// var s = ByteArrayOutputStream.$new();
var s = FileOutputStream.$new(filesDir + "/test.cert");
store.call(instance, s, charArray);
// var b = s.toByteArray();
// console.log(byteArrayToHex(b))
} catch (e) {
console.warn(e);
}
}
})
}
function java_security_KeyStore__load(fn) {
try {
var KeyStore = Java.use('java.security.KeyStore')
KeyStore['load']
.overload('java.io.InputStream', '[C')
.implementation = function (stream, charArray) {
if (stream == null) {
this.load(stream, charArray);
return;
}
console.log("[KeyStore.load]");
this.load(stream, charArray);
if (fn) {
fn(this, charArray);
}
console.log("\t", stream.toString());
console.log("\t", this.getType());
console.log("\t", charArrayToAsciiHex(charArray))
}
} catch (e) {
console.warn(e)
}
}
function byteArrayToHex(bytes) {
var hex = [];
var data = [];
for (var i = 0; i < bytes['length']; i++) {
hex.push(('0' + (bytes[i] & 0xFF).toString(16)).slice(-2));
}
return hex.join(' ');
}
function charArrayToAsciiHex(charArray) {
var hex = [];
var data = [];
for (var i = 0; i < charArray['length']; i++) {
hex.push(('0' + (charArray[i].charCodeAt(0) & 0xFF).toString(16)).slice(-2));
data.push(charArray[i]);
}
return hex.join(' ') + '\t' + data.join('');
}