关于抓包的总体的思路和方法. 2025年1月14号

1, 抓包的单向证书, 通过实际的例子去解读这个问题.

 

 

, 服务端证书, 校验原理.  --- 本质

---> 后续只要发送, 就能发送过去.  接收到请求, 才能,  返回数据. 才能集成证书.

通过离线的代码python  和  加上证书, 脱离app 做操作.

如果有服务端证书校验[app自带的凭证还要交给--> 服务器校验!!! ],  你的charles 没有证书,使用抓包工具自带证书, 你也不能做到.  ----> 找到
服务端可以绕过证书校验,就可以返回值.  使用 Charles 可以愉快的进行抓包了.

绕过:    1, 找到集成在手机上的证书.p12 . 或者 .bks 后缀而存在的.

2, 需要找到证书的密码,  (导入证书需要)


手机集成客户端校验,   piner, 集成 sha256 ,  集成服务端,校验  + p12 的证书, + 密码,

举例子的代码:

KeyStore.getInstance("BKS")   ===>
KeyStore.getInstance("PKCS12")   ==> 这两种不同的格式的证书.
===> 使用了,    openRawResource 在 Raw 这个目录下存在的
===> 使用额,    getAssets().open("TLS");  就是在  Assets这个目录下存在.

Request 请求的证书, 需要把  Request 发送过去.

hook密码

Java.perform(function () {
    var KeyStore = Java.use("java.security.KeyStore");

    KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (v1, v2) {
        var pwd = Java.use("java.lang.String").$new(v2);  // string的构造方法,传递一个字符数组参数,转换为, 放在hook脚本里面, java.use, 实例化一个字符串,如果有参数,就把对象传递进去,那么恰好就是字符数组.
        console.log('\n------------')  // 把一个字符数组,转换为字符串, 
        console.log("类型:" + this.getType());  // 把他的类型加入了进来, 传入 PKCS12,  
        console.log("密码:" + pwd);
        console.log(JSON.stringify(v1));   //JSON.stringify 就可以将他到底是哪个类, 就可以把他给显示出来, 就可以知道她到底在哪个目录, 本质文件在哪里, 还需要给他拿到, 
        //console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));  // 这里输出调用栈,可以直接,
        var res = this.load(v1, v2); // 当我们hook keyLoad 时候,可以知道当前, 当前的读取的格式到底是什么. V1 是第一个参数输入流执行的参数, v2 是第二个参数, 只有内存地址.,
        return res;
    };
});
// frida -U -f com.paopaotalk.im -l 1.hook_password.js
// 111111

那么 泡泡聊天,就能够测试,这个脚本了.

找到  ,
类型:BKS
密码:111111
"<instance: java.io.InputStream, $className: android.content.res.AssetManager$AssetInputStream>"
找到 Asset目录,有没有BKS 文件, 如果有,那么很有可能就是证书.

尝试解压 泡泡聊天 apk, 找到  Asset 目录和 后缀为 bks结尾的文件.
===>  如果有多个 bks 结尾的文件, 丢到 搜索引擎里面可以看到, 是否我们想要的.

-----> 如果想要直接确定, hook时候直接使用,转存方式,在参数中直接实现写入到本地目录 . 只要内容拿到了, 直接写入到我自定的文件即可.
===> 通用的导出证书的脚本.

===> 导出证书时候, 一定要把 相关的  sd卡的 读写,  手机存储,  一定要开启.

Java.perform(function () {
    var KeyStore = Java.use("java.security.KeyStore");
    var String = Java.use("java.lang.String");


    KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (inputStream, v2) {
        var pwd = String.$new(v2);
        console.log('\n------------')
        console.log("密码:" + pwd, this.getType());

        // 我们这里写了一个判断, 因为如果这个文件,  等于一个 bks,  那么就让他走这里的代码,
        if (this.getType() === "BKS") {   // 这段代码,本质, 读取到java中的内容,并且写入到文件当中.  ======== 如果是其他类型, 直接照着修改. p12或其他
            var myArray = new Array(1024);    //================================> 而且这个字节数组默认是 1024个字节.
            for (var i = 0; i < myArray.length; i++) {
                myArray[i] = 0x0;               // 默认里面放置的是  0.
            }
            var buffer = Java.array('byte', myArray);   // 这里我们创建一个 缓冲区, buffer.  里面是字节数组. ,大小默认是1024个字节. 表示我创建一个1024的字节.

            // 核心的代码看这里,  通过 这里实例化 java中的一个file对象, 创建一个文件, 目标在于 远程安卓手机上的目录和远程内存. 并且加上 时间戳的 bks 后缀结尾.
            var file = Java.use("java.io.File").$new("/sdcard/Download/paopao-" + new Date().getTime() + ".bks");   //  ======== 如果是其他类型, bks直接照着修改. p12或其他
            var out = Java.use("java.io.FileOutputStream").$new(file);  // 创建一个输出流, 根据这个文件进行关联.
            var r;
            while ((r = inputStream.read(buffer)) > 0) {    // 通过 这个 while 将输出流写入到,  这个文件. 最多是去读取 1024 字节.[37.6--泡泡hook导出证书,mp4==> 08:48 的位置.]
                out.write(buffer, 0, r);    // 中间这里是写入到输出流.
            }
            console.log("save success!")
            out.close()                 // 通过 out.close 代表写完了.
        }

        var res = this.load(inputStream, v2);   // 最后, 本质上你需要到 input 里面去 一点点儿的 读取,  然后一点儿一点儿写入到我们的文件.
        return res;
    };

});

// frida -U -f com.paopaotalk.im -l 2.hook_save.js

 

==> 证书 拿到 + 密码,  也可以通过 charles 携带也可以.

===> 总结:

1, hook KeyStore 的load 方法, 获取调用栈. 谁传入了这个,就是 inputstream 对象.

java.lang.Throwable
        at java.security.KeyStore.load(Native Method)  // 这里是最后调用的--> 调用的结束位置
        at com.base.httpnet.https.HttpsUtils.prepareKeyManager(HttpsUtils.java:2)  //---> 调用逐渐向上寻找.
        at com.base.httpnet.https.HttpsUtils.getSslSocketFactoryBase(HttpsUtils.java:2)
        at com.base.httpnet.https.HttpsUtils.getSslSocketFactory(HttpsUtils.java:4)
        at com.vvchat.vcapp.QYXApplication.initOkHttpData(QYXApplication.java:3)
        at com.vvchat.vcapp.QYXApplication.inital(QYXApplication.java:12)
        at com.vvchat.vcapp.QYXApplication.onCreate(QYXApplication.java:5)
        at com.stub.StubApp.onCreate(Unknown Source:47)
        at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1283)
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6880)

2 ,你知道他是怎么来的,就知道他是哪个对象了.

2, 反编译 apk + 脱壳

使用 frida-dexdump出来以后,直接,  全部托进去,  发现为空, 那么 , 一个个尝试, 把为空的删除,就能够找到了.
(直接使用1-5 ,或者 1-10 , 大段去找,谁中断他们,删除谁. )

3, 分析, 根据调用栈一步步向上去找. ------>

根据这个" instance.load(inputStream,   str.toCharArray());"--> 前面是 两个参数, 前面是证书, 后面是密码,  这里的 str 即应是密码.

inputStream 应该是他的输入流.

找到他的上一步,---->   java.security.KeyStore.load(Native Method)

public static void com.vvchat.vcapp.QYXApplication.initOkHttpData() {
        try {
            if (!RxHttpNet.isInit()) {
                HttpsUtils.SSLParams sslSocketFactory = HttpsUtils.getSslSocketFactory(c.getAssets().open("client.bks"), "111111", new InputStream[0]); // 打开这里,获取他的密码. 通过这里代码的回溯,找到这里的, 证明我们这里的分析是正确的.如果分析以下,你还想要证明, 我们是否还需要一个hook.
                a0 f2 = f(sslSocketFactory.trustManager, sslSocketFactory.sSLSocketFactory);
                RxHttpNet.getInstance().setOkHttpClient(f2);
                setOkHttpDataHeaders();
                RxHttpNet.getInstance().init(getAppContext());
                Glide.get(c).getRegistry().replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(f2));
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

------ > 奇怪的提示--- > 找不到相关的类和方法和 包.

---> 明明脱壳出来,反编译后发现有, 他却提示没有?,,,=====> 这在远程的安卓机器上, 说的就是壳导致的,就是手机里面对你的dex文件进行了壳, 启动的一瞬间, dex 先要 loading 到 memory,  之后,才能 运行相对应的功能,   如果你都没有 loading  finished, 的话, 那么,  就会报错,
就可以用一个 setTime out的延迟, 延时,

 

尤其对于第三方的包,  加入一个 settime out 的延迟,对应地去 修改.

 

bks 证书, 必须 --[portecle-1.11/证书转换程序java jar ]-to ---> p12 证书--> 导入 charlles ,就可以使用了.

charles的设置-- >  ssl proxy,  客户端证书,  找到 Client_Certificates, add--->

 

案例: 美之图app ,3,5,3.

通过测试在 android 13的位置上没有办法使用,  frida 16.1.3 可以在安卓10(小米8)上能够跑起来.

类型:pkcs12
密码:uX39!dd$#rr_XIyb%
"<instance: java.io.InputStream, $className: android.content.res.AssetManager$AssetInputStream>"
java.lang.Throwable
        at java.security.KeyStore.load(Native Method)
        at com.deepe.c.j.c.b.b(Unknown Source:32)
        at com.deepe.c.j.c.b.a(Unknown Source:39)
        at com.deepe.c.j.e.d.getSslSocketFactory(Unknown Source:20)
        at com.deepe.c.j.d.g.a(Unknown Source:42)
        at com.deepe.c.j.d.g.a(Unknown Source:120)
        at com.deepe.c.j.d.a.a(Unknown Source:33)
        at com.deepe.c.j.h.run(Unknown Source:69)

// frida -D 10.10.10.200:5555 -f com.mmzztt.app -l "/home/calleng/p9/Mikrom2.0/JS_editor/com_paopaotalk_im_HOOK证书密码__防止截图黑屏.js"

通过上面的可以得到,   证书使用 pkcs12 处理,  所以我们可以 解压后,看看有没有 p12 结尾的, Asset目录里面确认以下.看看.

 

成功取得数据的证书绑定验证的脚本

from requests_pkcs12 import get

# 定义请求头
headers = {
    "Accept": "*/*",
    "User-Agent": "Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 mztapk",
    "Referer": "https://app.mmzztt.com",
    "Charset": "UTF-8",
    "Host": "api.iimzt.com"
}

# 定义 URL
url = "https://api.iimzt.com/app/post/p?id=91452"

# 如果需要 PKCS12 证书
pkcs12_filename = "/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/妹子图-1737123511881.pkcs12"  # 替换为你的证书路径
pkcs12_password = "uX39!dd$#rr_XIyb%"  # 替换为你的证书密码

# 发送 GET 请求
response = get(
    url,
    headers=headers,
    pkcs12_filename=pkcs12_filename,  # 如果需要客户端证书
    pkcs12_password=pkcs12_password,  # 如果需要客户端证书
    verify=False  # 验证服务器证书
)

# 输出响应内容
print(response.status_code)
print(response.text)

关于证书转换的问题, 以前默认是 p12 证书, 我要使用 request 去请求,那么我使用,   Portecle 这个 java工具也可以转换.

通过request的模式访问

import requests

# 客户端证书和私钥文件路径
cert_file = '/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/certificate.pem'  # 证书文件路径
key_file = '/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download//private_key.pem'  # 私钥文件路径

# 请求的 URL
url = "https://api.iimzt.com/app/post/p?id=91452"

# 请求头
headers = {
    "Accept": "*/*",
    "User-Agent": "Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 mztapk",
    "Referer": "https://app.mmzztt.com",
    "Charset": "UTF-8",
    "Host": "api.iimzt.com"
}

# 发送请求
try:
    response = requests.get(
        url,
        headers=headers,
        cert=(cert_file, key_file),  # 使用客户端证书和私钥
        verify=False  # 不验证服务器证书
    )

    # 输出响应内容
    print("Status Code:", response.status_code)
    print("Response Body:", response.text)

except requests.exceptions.RequestException as e:
    print("Request failed:", e)



# ==============检测 文件是否存在.====================
import os

print("Certificate file exists:", os.path.exists(cert_file))
print("Key file exists:", os.path.exists(key_file))

with open(cert_file, 'r') as f:
    print("Certificate content:", f.read())

with open(key_file, 'r') as f:
    print("Key content:", f.read())
# ==============检测 文件是否存在.====================



# 类型:pkcs12
# 密码:uX39!dd$#rr_XIyb%
# "<instance: java.io.InputStream, $className: android.content.res.AssetManager$AssetInputStream>"

# curl -H "Accept: */*" -H "User-Agent: Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 mztapk" -H "Referer: https://app.mmzztt.com" -H "Charset: UTF-8" -H "Host: api.iimzt.com" --compressed "https://api.iimzt.com/app/list/posts?type=post&order=best&page=2"

# 默认requests不支持直接使用p12格式的证书,所以需要将p12转换成pem才可以,
# 注意1,   request  需要 cert_file   证书文件(certificate.pem)
# 注意2,   request  需要  私钥文件(private_key.pem):
# 注意3,   目前的高版本的 openssl 3.x , 不再兼容老旧的算法.
# 在 Python 中使用分离的证书和私钥  ===>  将生成的 certificate.pem 和 private_key.pem 文件路径替换到 Python 代码中:


# openssl pkcs12 -in  meizhitu_2025.p12  -out certificate.pem -nokeys -password 'pass:uX39!dd$#rr_XIyb%' -legacy
# openssl pkcs12 -in meizhitu_2025.p12  -out private_key.pem -nocerts -nodes -password 'pass:uX39!dd$#rr_XIyb%' -legacy

 

 

通过curl 的模式访问, 证书加密的数据

第一种如果不成功

curl -v -X GET \
  -H "Accept: */*" \
  -H "User-Agent: Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 mztapk" \
  -H "Referer: https://app.mmzztt.com" \
  -H "Charset: UTF-8" \
  -H "Host: api.iimzt.com" \
  --cert "/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/certificate.pem" \
  --key "/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/private_key.pem" \
  --insecure \
  "https://api.iimzt.com/app/post/p?id=91452"

使用第二种

curl -v -X GET  \
-H "Accept: */*"  \
-H "User-Agent: Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 mztapk"   \
-H "Referer: https://app.mmzztt.com"  \
-H "Charset: UTF-8" \
-H "Host: api.iimzt.com"   \
--cert "/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/certificate.pem"   \
--key "/home/calleng/p9/Mikrom2.0/season5/day36 抓包-服务端证书/Download/private_key.pem"   \
--insecure   "https://api.iimzt.com/app/post/p?id=91452"