Skip to content

Instantly share code, notes, and snippets.

@h1code2
Last active January 23, 2024 06:32
Show Gist options
  • Save h1code2/bce61f462bcc5a6fd2fe055499781303 to your computer and use it in GitHub Desktop.
Save h1code2/bce61f462bcc5a6fd2fe055499781303 to your computer and use it in GitHub Desktop.
FRIDA抓包相关笔记/常用操作 #frida #抓包 #hook #okhttp #proxy #pinning # ssl

FRIDA抓包相关笔记/常用操作

原文链接:https://api-caller.com/2019/11/05/capture-note/

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

珍惜Any 之前提过 一种思路 是遍历所有类, 按照特征去判断校验类和方法.

此处的思路则是, 先跑一遍并让它失败, 找到特征打印堆栈, 即可锁定位置, 再行 hook 即可.

先去看 okhttp 这一块的 源码:

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

此处挑选第四条:

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
Java.use('okhttp3.CertificatePinner')['a'].overload('java.lang.String', 'java.util.List').implementation = function (hostname, peerCertificates) { 
	console.log(hostname);        
}

hook 设置代理

遇到个奇怪的样本, 设置了 NO_PROXY, 一般这种情况我用 drony, 但或许是实现的问题, 就会造成包体有问题, 遂考虑用 hook 代码设置代理. 先分析是什么网络框架, 发现是 okhttp3, 查看 源码 中返回 proxy 的地方, 编写代码:

// 先构造一个 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 证书和密码.

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('');
}

很早之前直接解析OKhttp response方法

Java.perform(function () {
    // Java.openClassFile("/data/local/tmp/gson.dex").load();
    // const Gson = Java.use('com.google.gson.Gson').$new(); // 使用Gson将对象类转成Json对象时出现\u003d 、\u0027等情况的问题
    // const gson = Java.use('com.google.gson.GsonBuilder').$new().disableHtmlEscaping().create();

    /**
     * 解析数据包Response
     */
    const getOkHttpClient = Java.use("cn.oyohotels.android.core.net.ApiProxyFactory$getOkHttpClient$1");
    getOkHttpClient.intercept.implementation = function (chain) {
        console.log("\ngetOkHttpClient -> intercept:");

        const Response = Java.use("okhttp3.Response");
        const ResponseBody = Java.use("okhttp3.ResponseBody");
        const Request = Java.use("okhttp3.Request");
        // const Headers = Java.use("okhttp3.Headers");

        const tmp_req = chain.request();
        const request = Java.cast(tmp_req, Request);

        console.log("请求基本信息:\n" + request.toString());
        const header = request.headers();
        console.log("请求Headers:\n" + header.toString());

        const requestBody = request.body();
        const method = request.method();
        if (method == "POST") {
            const Buffer = Java.use("okio.Buffer");
            const bufferedSink = Buffer.$new();
            requestBody.writeTo(bufferedSink);
            console.log("请求参数:\n" + bufferedSink.r());
        }
        // const response = chain.proceed(request); // reqsponse
        const ret = this.intercept(chain); // 函数执行结果,response
        // showStacks();
        const response = Java.cast(ret, Response);
        var responseBody = Java.cast(response.body(), ResponseBody);
        console.log("请求结果:\n" + responseBody.string() + "\n");

        const ret2 = this.intercept(chain); // 关于调用string()方法后会close,临时再发一次..
        return ret2;
    };
})

Mtop协议切换

APP有两套方案时,使用Hook的办法让APP不走socket,走http。 使用mtopsdk(Android Mtop网关接入文档。https://help.aliyun.com/document_detail/69785.html)Hook Mtop让其不走socket。其sdk对外开放,使用sdk的APP均可以使用。 https://blog.csdn.net/haoren_xhf/article/details/90449978

var SwitchConfig = Java.use("mtopsdk.mtop.global.SwitchConfig");
SwitchConfig.isGlobalSpdySwitchOpen.implementation = function () {
    var ret = this.isGlobalSpdySwitchOpen();
    log("ret", ret);
    return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment