抓包二三事

Posted by API Caller on November 5, 2019

抓包应该是被问到最多的事情了, 记录一些片段.

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. 锁定了几个选择:

  1. X509Certificate.getPublicKey / getEncoded
  2. ByteString.sha1
  3. StringBuilder.append
  4. 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('');
}