移动安全_抓包__底层通信_http和SSL请求的本质

http请求的本质

例如:你在Python中使用requests模块发送一个http请求,其底层就是使用socket模块+TCP实现发送的请求。

import requests

res = requests.get("http://192.168.1.37:8080/index")
print(res.text)

如果基于底层的socket模块+TCP协议实现如下:

import socket

client = socket.socket()
client.connect(('192.168.1.37', 8080))

content_list = []
content_list.append("GET /index HTTP/1.1\r\n")
content_list.append("host: wiki.mikecrm.com\r\n")
content_list.append("user-Agent: test\r\n")
content_list.append("\r\n")
content_string = "".join(content_list)

client.sendall(content_string.encode('utf-8'))

response = b""
while True:
    chunk = client.recv(1024)
    if not chunk:
        break
    response += chunk

print(response.decode('utf-8'))

client.close()

Http请求DEMO

2.1 http请求

详见示例  ---  net.demo2.netd10_ssl01

public class MainActivity extends AppCompatActivity {

    private Button btn1; // 定义一个按钮
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this); // 启用边到边显示
        setContentView(R.layout.activity_main);// 设置布局文件
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {// 处理窗口内边距,  适应系统栏
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 初始化按钮
        btn1 = findViewById(R.id.btn1); //通过 ID 找到按钮
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doRequest();
            }
        });
    }


    private void doRequest() {
        new Thread() {
            @Override
            public void run() {
                try {
                    // http://wiki.mikecrm.com/index?ajax=1&page=2
                    Socket socket = new Socket("192.168.1.37", 8080);

                    // 1.构造请求头
                    StringBuilder sb = new StringBuilder();
                    sb.append("GET /index HTTP/1.1\r\n");
                    sb.append("host: wiki.mikecrm.com\r\n");
                    sb.append("user-Agent: test\r\n");
                    sb.append("\r\n");


                    // 2.写入数据(发送数据)
                    // java.net.SocketOutputStream

                    OutputStream outputStream = socket.getOutputStream();
                    outputStream.write(sb.toString().getBytes());
                    Log.e("outputStream的类 => ", outputStream.getClass().getName());


                    // 3.读取数据(获取数据)
                    // java.net.SocketInputStream
                    InputStream inputStream = socket.getInputStream();
                    Log.e("inputStream的类 => ", inputStream.getClass().getName());
                    while (true) {
                        byte[] buffer = new byte[1024];
                        int len = inputStream.read(buffer, 0, buffer.length);
                        if (len == -1) {
                            break;
                        }
                        Log.e("读取相应内容 =>", new String(Arrays.copyOf(buffer, len)));
                    }



                    socket.close();

                } catch (Exception ex) {
                    Log.e("Main", "网络请求异常" + ex);
                }
            }
        }.start();
    }
}

http请求写入

请求数据的写入是调用的`SocketOutputStream`的`write`方法。

// java.net.SocketOutputStream
OutputStream outputStream = socket.getOutputStream();
outputStream.write(sb.toString().getBytes());

http请求写入_调用路径

package java.net;

class SocketOutputStream extends FileOutputStream {

    private native void socketWrite0(FileDescriptor var1, byte[] var2, int var3, int var4) throws IOException;  // 底层 0 

    private void socketWrite(byte[] b, int off, int len) throws IOException {   // 底层 1 +
        if (len > 0 && off >= 0 && len <= b.length - off) {
            FileDescriptor fd = this.impl.acquireFD();

            try {
                this.socketWrite0(fd, b, off, len);  // 调用 socketWrite0 Native层
            } catch (SocketException var9) {
                if (this.impl.isClosedOrPending()) {
                    throw new SocketException("Socket closed");
                }

                throw var9;
            } finally {
                this.impl.releaseFD();
            }

        } else if (len != 0) {
            throw new ArrayIndexOutOfBoundsException("len == " + len + " off == " + off + " buffer length == " + b.length);
        }
    }

    public void write(int b) throws IOException {  // 底层 2 + 
        this.temp[0] = (byte)b;
        this.socketWrite(this.temp, 0, 1);
    }

}

所以,底层发送的http请求的数据就是执行 `java.net.SocketOutputStream` 中的 `socketWrite0`方法。

那么,如果我们对这个方法进行Hook,是不是就可以获取到所有http请求的数据了。

 http请求写入_hook验证

Java.perform(function () {
    // 使用Java.use获取Java类的引用
    var SocketOutputStream = Java.use('java.net.SocketOutputStream');  // 获取SocketOutputStream类的引用
    var HexDump = Java.use("com.android.internal.util.HexDump");  // 获取HexDump类的引用,用于十六进制转储
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");  // 获取ByteString类的引用,用于处理字节数据

    // 重载SocketOutputStream类的socketWrite0方法
    SocketOutputStream.socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, b, off, len) {
        // 打印传入的参数
        console.log("参数:", fd, b, off, len);

        // 使用HexDump.dumpHexString方法将字节数组转换为十六进制字符串并打印
        console.log(HexDump.dumpHexString(b, off, len), "\n");

        // 使用ByteString.of方法将字节数组转换为UTF-8字符串并打印
        console.log(ByteString.of(b).utf8(), "\n");

        // 调用原始的socketWrite0方法并返回结果
        return this.socketWrite0(fd, b, off, len);
    };
});

// frida -UF -l  1.hook.js
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

//  frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook.js'

// (.venv) calleng@tuxedo-os:~/p9/Mikrom2.0/season5/day36 抓包-服务端证书$ frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook.js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd10_ssl01`. Resuming main thread!
// [MI 8::net.demo2.netd10_ssl01 ]-> 参数: java.io.FileDescriptor@8c550c7 71,69,84,32,47,105,110,100,,120,32,72,84,84,80,47,49,46,49,13,10,104,111,115,116,58,32,119,105,107,105,46,109,105,107,101,99,114,109,46,99,111,109,13,10,117,115,101,114,45,65,103,101,110,116,58,32,116,101,115,116,13,10,13,10 0 65
//
// 0x00000000 47 45 54 20 2F 69 6E 64 65 78 20 48 54 54 50 2F GET./index.HTTP/
// 0x00000010 31 2E 31 0D 0A 68 6F 73 74 3A 20 77 69 6B 69 2E 1.1..host:.wiki.
// 0x00000020 6D 69 6B 65 63 72 6D 2E 63 6F 6D 0D 0A 75 73 65 mikecrm.com..use
// 0x00000030 72 2D 41 67 65 6E 74 3A 20 74 65 73 74 0D 0A 0D r-Agent:.test...
// 0x00000040 0A                                              .
//
// GET /index HTTP/1.1
// host: wiki.mikecrm.com
// user-Agent: test

 

http响应获取_实现

// java.net.SocketInputStream
InputStream inputStream = socket.getInputStream();
Log.e("inputStream的类 => ", inputStream.getClass().getName());
while (true) {
    byte[] buffer = new byte[1024];
    int len = inputStream.read(buffer, 0, buffer.length);
    if (len == -1) {
        break;
    }
    Log.e("读取相应内容 =>", new String(Arrays.copyOf(buffer, len)));
}

http响应获取_调用路径

package java.net;


class SocketInputStream extends FileInputStream {

    private native int socketRead0(FileDescriptor fd,
                                   byte b[], int off, int len,
                                   int timeout)
        throws IOException;


    private int socketRead(FileDescriptor fd,
                           byte b[], int off, int len,
                           int timeout)
        throws IOException {
        return socketRead0(fd, b, off, len, timeout);
    }


    public int read(byte b[], int off, int length) throws IOException {
        return read(b, off, length, impl.getTimeout());
    }

    int read(byte b[], int off, int length, int timeout) throws IOException {
        int n;

        // EOF already encountered
        if (eof) {
            return -1;
        }

        // connection reset
        if (impl.isConnectionReset()) {
            throw new SocketException("Connection reset");
        }

        // bounds check
        if (length <= 0 || off < 0 || length > b.length - off) {
            if (length == 0) {
                return 0;
            }
            throw new ArrayIndexOutOfBoundsException("length == " + length
                    + " off == " + off + " buffer length == " + b.length);
        }

        // acquire file descriptor and do the read
        FileDescriptor fd = impl.acquireFD();
        try {
            // Android-added: Check BlockGuard policy in read().
            BlockGuard.getThreadPolicy().onNetwork();
            n = socketRead(fd, b, off, length, timeout);
            if (n > 0) {
                return n;
            }
        } catch (ConnectionResetException rstExc) {
            impl.setConnectionReset();
        } finally {
            impl.releaseFD();
        }

        /*
         * If we get here we are at EOF, the socket has been closed,
         * or the connection has been reset.
         */
        if (impl.isClosedOrPending()) {
            throw new SocketException("Socket closed");
        }
        if (impl.isConnectionReset()) {
            throw new SocketException("Connection reset");
        }
        eof = true;
        return -1;
    }

}

所以,底层发送的http响应的的数据就是执行 `java.net.SocketInputStream` 中的 `socketRead0`方法。

那么,如果我们对这个方法进行Hook,是不是就可以获取到所有http请求的数据了。

http响应获取_hook验证

Java.perform(function () {
    var SocketInputStream = Java.use('java.net.SocketInputStream');
    var HexDump = Java.use("com.android.internal.util.HexDump");
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");

    SocketInputStream.socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, b, off, len, timeout) {

        var res = this.socketRead0(fd, b, off, len, timeout);
        // console.log(HexDump.dumpHexString(b, off, len), "\n")
        console.log(ByteString.of(b).utf8(), "\n");
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return res;

    };
});

// frida -UF -l  2.hook.js
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

//  frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook_响应获取.js'

// (.venv) calleng@tuxedo-os:~/p9/Mikrom2.0/season5/day36 抓包-服务端证书$ frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook_响应获取.js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// HTTP/1.1 200 OK
// Server: Werkzeug/3.1.3 Python/3.9.13
// Date: Thu, 23 Jan 2025 11:34:45 GMT
// Content-Type: application/json
// Content-Length: 34
// Connection: close
//
//
//
// java.lang.Throwable
//         at java.net.SocketInputStream.socketRead0(Native Method)
//         at java.net.SocketInputStream.socketRead(SocketInputStream.java:119)
//         at java.net.SocketInputStream.read(SocketInputStream.java:176)
//         at java.net.SocketInputStream.read(SocketInputStream.java:144)
//         at net.demo2.netd10_ssl01.MainActivity$2.run(MainActivity.java:73)
//
// {"code":1000,"message":"success"}
//
//
// java.lang.Throwable
//         at java.net.SocketInputStream.socketRead0(Native Method)
//         at java.net.SocketInputStream.socketRead(SocketInputStream.java:119)
//         at java.net.SocketInputStream.read(SocketInputStream.java:176)
//         at java.net.SocketInputStream.read(SocketInputStream.java:144)
//         at net.demo2.netd10_ssl01.MainActivity$2.run(MainActivity.java:73)
//
//
//
// java.lang.Throwable
//         at java.net.SocketInputStream.socketRead0(Native Method)
//         at java.net.SocketInputStream.socketRead(SocketInputStream.java:119)
//         at java.net.SocketInputStream.read(SocketInputStream.java:176)
//         at java.net.SocketInputStream.read(SocketInputStream.java:144)
//         at net.demo2.netd10_ssl01.MainActivity$2.run(MainActivity.java:73)

 

so级别

上述的请求和写入,都是在java层面进行hook的获取,实际上真正的请求会调用so层的代码,最终将请求发送过去。

底层调用路径_发送_接收


// ==========================================> 一个发送
package java.net;

class SocketOutputStream extends FileOutputStream {

    private native void socketWrite0(FileDescriptor var1, byte[] var2, int var3, int var4) throws IOException;  // 底层 0 

}


//============================================> 一个接收
package java.net;


class SocketInputStream extends FileInputStream {

    private native int socketRead0(FileDescriptor fd,byte b[], int off, int len,int timeout)  throws IOException;

}

 libopenjdk.so

`socketRead0`和`socketWrite0`

OkHttp3_底层_JavaHook__寻找socketRead0和socketWrite0的hook的脚本

Java.perform(function (){
    const apiResolver = new ApiResolver('module');
    apiResolver.enumerateMatches('exports:*!*socket*0*').forEach(function (v){
        console.log(v.name);
    });
});

// frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook__socketRead0和socketWrite0的hook的脚本.js'

// frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_JavaHook__socketRd0和socketWrite0的hook的脚本.js's'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd10_ssl01`. Resuming main thread!
// [MI 8::net.demo2.netd10_ssl01 ]-> /apex/com.android.runtime/lib64/libopenjdk.so!SocketInputStream_socketRead0
// /apex/com.android.runtime/lib64/libopenjdk.so!Java_sun_nio_ch_Net_socket0
// /apex/com.android.runtime/lib64/libopenjdk.so!SocketOutputStream_socketWrite0

 

http请求和响应_hook

/apex/com.android.runtime/lib64/libopenjdk.so

Java.perform(function () { // 使用Frida的Java.perform函数在Java运行时中执行代码
    const NET_Send = Module.getExportByName('libopenjdk.so', 'NET_Send'); // 获取libopenjdk.so中的NET_Send函数地址
    const NET_Read = Module.getExportByName('libopenjdk.so', 'NET_Read'); // 获取libopenjdk.so中的NET_Read函数地址

    Interceptor.attach(NET_Send, { // 拦截NET_Send函数
        onEnter(args) { // 当进入NET_Send函数时执行
            console.log('write call'); // 打印日志,表示进入NET_Send函数
            //console.log(hexdump(args[1], {length: args[2].toInt32()})); // 打印内存十六进制数据(注释状态)
            console.log(Memory.readByteArray(args[1], args[2].toInt32())); // 读取并打印内存中的字节数组
        }
    });

    Interceptor.attach(NET_Read, { // 拦截NET_Read函数
        onEnter(args) { // 当进入NET_Read函数时执行
            console.log('read call'); // 打印日志,表示进入NET_Read函数
            this.buf = args[1]; // 保存读取的缓冲区地址到this.buf
        }, onLeave: function (retval) { // 当离开NET_Read函数时执行
            retval |= 0; // 将retval强制转换为32位整数
            if (retval <= 0) { // 如果返回值小于等于0,直接返回
                return;
            }
            console.log(Memory.readByteArray(this.buf, retval)); // 读取并打印缓冲区中的数据
        }
    });
});

// frida -D 10.10.10.200:5555  -f net.demo2.netd10_ssl01  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SoHook_LibOpenJDK_请求和响应.js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd10_ssl01`. Resuming main thread!
// [MI 8::net.demo2.netd10_ssl01 ]->
// write call ============>
//            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  47 45 54 20 2f 69 6e 64 65 78 20 48 54 54 50 2f  GET /index HTTP/
// 00000010  31 2e 31 0d 0a 68 6f 73 74 3a 20 77 69 6b 69 2e  1.1..host: wiki.
// 00000020  6d 69 6b 65 63 72 6d 2e 63 6f 6d 0d 0a 75 73 65  mikecrm.com..use
// 00000030  72 2d 41 67 65 6e 74 3a 20 74 65 73 74 0d 0a 0d  r-Agent: test...
// 00000040  0a                                               .
// read call ============>
//            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d  HTTP/1.1 200 OK.
// 00000010  0a 53 65 72 76 65 72 3a 20 57 65 72 6b 7a 65 75  .Server: Werkzeu
// 00000020  67 2f 33 2e 31 2e 33 20 50 79 74 68 6f 6e 2f 33  g/3.1.3 Python/3
// 00000030  2e 39 2e 31 33 0d 0a 44 61 74 65 3a 20 54 68 75  .9.13..Date: Thu
// 00000040  2c 20 32 33 20 4a 61 6e 20 32 30 32 35 20 31 32  , 23 Jan 2025 12
// 00000050  3a 31 31 3a 31 31 20 47 4d 54 0d 0a 43 6f 6e 74  :11:11 GMT..Cont
// 00000060  65 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63  ent-Type: applic
// 00000070  61 74 69 6f 6e 2f 6a 73 6f 6e 0d 0a 43 6f 6e 74  ation/json..Cont
// 00000080  65 6e 74 2d 4c 65 6e 67 74 68 3a 20 33 34 0d 0a  ent-Length: 34..
// 00000090  43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73  Connection: clos
// 000000a0  65 0d 0a 0d 0a                                   e....
// read call ============>
//            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  7b 22 63 6f 64 65 22 3a 31 30 30 30 2c 22 6d 65  {"code":1000,"me
// 00000010  73 73 61 67 65 22 3a 22 73 75 63 63 65 73 73 22  ssage":"success"
// 00000020  7d 0a                                            }.
// read call ============>

SSL请求的本质

SSL请求DEMO

详见示例:net.demo2.netd11_ssl02

public class MainActivity extends AppCompatActivity {

    private Button btn1; // 定义一个按钮
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this); // 启用边到边显示
        setContentView(R.layout.activity_main);// 设置布局文件
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {// 处理窗口内边距,  适应系统栏
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 初始化按钮
        btn1 = findViewById(R.id.btn1); //通过 ID 找到按钮
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doRequest();
            }
        });
    }
    private void doRequest() {
        new Thread() {
            @Override
            public void run() {
                try {
                    // https://api.luffycity.com/api/v1/course/actual/?limit=1&offset=0&category_id=9999

                    SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
                    SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("api.luffycity.com", 443);

                    // 1.构造请求头
                    StringBuilder sb = new StringBuilder();
                    sb.append("GET /api/v1/course/actual/?limit=1&offset=0&category_id=9999 HTTP/1.1\r\n");
                    sb.append("host: api.luffycity.com\r\n");
                    sb.append("user-Agent: test\r\n");
                    sb.append("\r\n");

                    // 2.写入数据(发送数据)
                    // com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream
                    OutputStream outputStream = socket.getOutputStream();
                    outputStream.write(sb.toString().getBytes());
                    Log.e("outputStream的类 => ", outputStream.getClass().getName());

                    // 3.读取数据(获取数据)
                    // com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream
                    InputStream inputStream = socket.getInputStream();
                    Log.e("inputStream的类 => ", inputStream.getClass().getName());

                    while (true) {
                        byte[] buffer = new byte[1024];
                        int len = inputStream.read(buffer, 0, buffer.length);
                        if (len == -1) {
                            break;
                        }
                        Log.e("读取相应内容 =>", new String(Arrays.copyOf(buffer, len)));
                    }

                    socket.close();

                } catch (Exception ex) {
                    Log.e("Main", "网络请求异常" + ex);
                }
            }
        }.start();
    }
}

SSL发送_接收_路径 A

// com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream
OutputStream outputStream = socket.getOutputStream();
outputStream.write(sb.toString().getBytes());
Log.e("outputStream的类 => ", outputStream.getClass().getName());

 

调用路径_[请求写入]_1_Step_A , org.conscrypt.ConscryptFileDescriptorSocket.$SSLOutputStream.$write

调用路径_[响应获取]_2_Step_A, org.conscrypt.ConscryptFileDescriptorSocket.$SSLInputStream.$read

安卓源码地址:[updated]

http://xrefandroid.com/android-10.0.0_r47/xref/external/conscrypt/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
/*
 * Copyright (C) 2017 The Android Open Source Project
 */

package org.conscrypt;

class ConscryptFileDescriptorSocket extends OpenSSLSocketImpl
        implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.AliasChooser,
                   SSLParametersImpl.PSKCallbacks {

    /**
     * This inner class provides input data stream functionality
     * for the OpenSSL native implementation. It is used to
     * read data received via SSL protocol.
     */
    private class SSLInputStream extends InputStream {
        /**
         * OpenSSL only lets one thread read at a time, so this is used to
         * make sure we serialize callers of SSL_read. Thread is already
         * expected to have completed handshaking.
         */
        private final Object readLock = new Object();

        SSLInputStream() {
        }

        /**
         * Reads one byte. If there is no data in the underlying buffer,
         * this operation can block until the data will be
         * available.
         */
        @Override
        public int read() throws IOException {
            byte[] buffer = new byte[1];
            int result = read(buffer, 0, 1);
            return (result != -1) ? buffer[0] & 0xff : -1;
        }

        /**
         * Method acts as described in spec for superclass.
         * @see java.io.InputStream#read(byte[],int,int)
         */
        @Override
        public int read(byte[] buf, int offset, int byteCount) throws IOException {
            Platform.blockGuardOnNetwork();

            checkOpen();
            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
            if (byteCount == 0) {
                return 0;
            }

            synchronized (readLock) {
                synchronized (ssl) {
                    if (state == STATE_CLOSED) {
                        throw new SocketException("socket is closed");
                    }

                    if (DBG_STATE) {
                        assertReadableOrWriteableState();
                    }
                }

                int ret =  ssl.read(
                        Platform.getFileDescriptor(socket), buf, offset, byteCount, getSoTimeout());
                if (ret == -1) {
                    synchronized (ssl) {
                        if (state == STATE_CLOSED) {
                            throw new SocketException("socket is closed");
                        }
                    }
                }
                return ret;
            }
        }

        void awaitPendingOps() {
            if (DBG_STATE) {
                synchronized (ssl) {
                    if (state != STATE_CLOSED) {
                        throw new AssertionError("State is: " + state);
                    }
                }
            }

            synchronized (readLock) {}
        }
    }

    /**
     * This inner class provides output data stream functionality
     * for the OpenSSL native implementation. It is used to
     * write data according to the encryption parameters given in SSL context.
     */
    private class SSLOutputStream extends OutputStream {
        /**
         * OpenSSL only lets one thread write at a time, so this is used
         * to make sure we serialize callers of SSL_write. Thread is
         * already expected to have completed handshaking.
         */
        private final Object writeLock = new Object();

        SSLOutputStream() {
        }

        /**
         * Method acts as described in spec for superclass.
         * @see java.io.OutputStream#write(int)
         */
        @Override
        public void write(int oneByte) throws IOException {
            byte[] buffer = new byte[1];
            buffer[0] = (byte) (oneByte & 0xff);
            write(buffer);
        }

        /**
         * Method acts as described in spec for superclass.
         * @see java.io.OutputStream#write(byte[],int,int)
         */
        @Override
        public void write(byte[] buf, int offset, int byteCount) throws IOException {
            Platform.blockGuardOnNetwork();
            checkOpen();
            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
            if (byteCount == 0) {
                return;
            }

            synchronized (writeLock) {
                synchronized (ssl) {
                    if (state == STATE_CLOSED) {
                        throw new SocketException("socket is closed");
                    }

                    if (DBG_STATE) {
                        assertReadableOrWriteableState();
                    }
                }

                ssl.write(Platform.getFileDescriptor(socket), buf, offset, byteCount,
                        writeTimeoutMilliseconds);

                synchronized (ssl) {
                    if (state == STATE_CLOSED) {
                        throw new SocketException("socket is closed");
                    }
                }
            }
        }

        void awaitPendingOps() {
            if (DBG_STATE) {
                synchronized (ssl) {
                    if (state != STATE_CLOSED) {
                        throw new AssertionError("State is: " + state);
                    }
                }
            }

            synchronized (writeLock) {}
        }
    }

}

SSL发送_接收_路径 B

调用路径_[请求写入]_1_Step_B, org.conscrypt.NativeSsl.$write

调用路径_[响应获取]_2_Step_B, org.conscrypt.NativeSsl.$read


http://xrefandroid.com/android-10.0.0_r47/xref/external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java

 

/*
 * Copyright (C) 2017 The Android Open Source Project
 */

package org.conscrypt;


/**
 * A utility wrapper that abstracts operations on the underlying native SSL instance.
 */
final class NativeSsl {

    // TODO(nathanmittler): Remove once after we switch to the engine socket.
    int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
            throws IOException {
        lock.readLock().lock();
        try {
            if (isClosed() || fd == null || !fd.valid()) {
                throw new SocketException("Socket is closed");
            }
            return NativeCrypto
                    .SSL_read(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
        } finally {
            lock.readLock().unlock();
        }
    }

    // TODO(nathanmittler): Remove once after we switch to the engine socket.
    void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
            throws IOException {
        lock.readLock().lock();
        try {
            if (isClosed() || fd == null || !fd.valid()) {
                throw new SocketException("Socket is closed");
            }
            NativeCrypto
                    .SSL_write(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
        } finally {
            lock.readLock().unlock();
        }
    }

}

SSL发送_接收_路径 C

调用路径_[请求写入]_1_Step_C, org.conscrypt.NativeCrypto.$SSL_write

调用路径_[响应获取]_2_Step_C, org.conscrypt.NativeCrypto.$SSL_read

http://xrefandroid.com/android-10.0.0_r47/xref/external/conscrypt/common/src/main/java/org/conscrypt/NativeCrypto.java

 

/*
 * Copyright (C) 2008 The Android Open Source Project
 */

package org.conscrypt;


@Internal
public final class NativeCrypto {
    // --- OpenSSL library initialization --------------------------------------

    /**
     * Reads with the native SSL_read function from the encrypted data stream
     * @return -1 if error or the end of the stream is reached.
     */
    static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc,
            byte[] b, int off, int len, int readTimeoutMillis) throws IOException;

    /**
     * Writes with the native SSL_write function to the encrypted data stream.
     */
    static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd,
            SSLHandshakeCallbacks shc, byte[] b, int off, int len, int writeTimeoutMillis)
            throws IOException;

}

 

SSL发送路径_调用栈_验证

frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_Java_[SSL_write].js'

 

Java.perform(function () { // 使用Frida的Java.perform方法,确保代码在Java上下文中执行
    var NativeCrypto = Java.use('com.android.org.conscrypt.NativeCrypto'); // 获取NativeCrypto类的引用,用于操作加密相关功能
    var HexDump = Java.use("com.android.internal.util.HexDump"); // 获取HexDump类的引用,用于将字节数据转换为十六进制字符串
    var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // 获取ByteString类的引用,用于处理字节数据

    NativeCrypto.SSL_write.implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout) { // 重写NativeCrypto.SSL_write方法的实现
        // ssl: 指向SSL对象的指针,表示当前的SSL连接
        // ssl_holder: SSL对象的持有者,用于管理SSL状态
        // fd: 文件描述符,表示底层的网络套接字
        // shc: SSL握手上下文,用于管理SSL握手过程
        // b: 字节数组,表示要写入的数据
        // off: 数据在字节数组中的起始偏移量
        // len: 要写入的数据长度
        // timeout: 写入操作的超时时间

        console.log(HexDump.dumpHexString(b, off, len), "\n") // 打印字节数组的十六进制表示(注释掉)
        console.log(ByteString.of(b).utf8(), "\n"); // 将字节数组转换为UTF-8字符串并打印
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); // 打印当前堆栈信息(注释掉)
        return this.SSL_write(ssl, ssl_holder, fd, shc, b, off, len, timeout); // 调用原始的SSL_write方法并返回结果
    };
});


// (.venv) calleng@tuxedo-os:~/p9/Mikrom2.0/season5/day36 抓包-服务端证书$ frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_Java_[SSL_write].js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd11_ssl02`. Resuming main thread!
// [MI 8::net.demo2.netd11_ssl02 ]->
// 0x00000000 47 45 54 20 2F 61 70 69 2F 76 31 2F 63 6F 75 72 GET./api/v1/cour
// 0x00000010 73 65 2F 61 63 74 75 61 6C 2F 3F 6C 69 6D 69 74 se/actual/?limit
// 0x00000020 3D 31 26 6F 66 66 73 65 74 3D 30 26 63 61 74 65 =1&offset=0&cate
// 0x00000030 67 6F 72 79 5F 69 64 3D 39 39 39 39 20 48 54 54 gory_id=9999.HTT
// 0x00000040 50 2F 31 2E 31 0D 0A 68 6F 73 74 3A 20 61 70 69 P/1.1..host:.api
// 0x00000050 2E 6C 75 66 66 79 63 69 74 79 2E 63 6F 6D 0D 0A .luffycity.com..
// 0x00000060 75 73 65 72 2D 41 67 65 6E 74 3A 20 74 65 73 74 user-Agent:.test
// 0x00000070 0D 0A 0D 0A                                     ....
//
// GET /api/v1/course/actual/?limit=1&offset=0&category_id=9999 HTTP/1.1
// host: api.luffycity.com
// user-Agent: test
//
//
//
// java.lang.Throwable  // 表示异常的根类,所有错误和异常的基类
//         at com.android.org.conscrypt.NativeCrypto.SSL_write(Native Method)  // 调用本地方法SSL_write,用于加密数据写入
//         at com.android.org.conscrypt.NativeSsl.write(NativeSsl.java:426)  // 调用NativeSsl的write方法,进一步处理加密数据写入
//         at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write(ConscryptFileDescriptorSocket.java:626) // 调用SSLOutputStream的write方法,将数据写入加密套接字
//         at java.io.OutputStream.write(OutputStream.java:75)     // 调用OutputStream的write方法,写入数据到输出流
//         at net.demo2.netd11_ssl02.MainActivity$2.run(MainActivity.java:65)   // 在MainActivity的第55行,线程执行时发生异常

SSL接收路径_调用栈_验证

frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_Java_[SSL_read].js'
Java.perform(function () { // 使用Frida的Java.perform方法,确保代码在Java上下文中执行
    var NativeCrypto = Java.use('com.android.org.conscrypt.NativeCrypto'); // 获取NativeCrypto类的引用,用于操作加密相关功能
    var HexDump = Java.use("com.android.internal.util.HexDump"); // 获取HexDump类的引用,用于将字节数据转换为十六进制字符串
    var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // 获取ByteString类的引用,用于处理字节数据

    NativeCrypto.SSL_read.implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout) { // 重写NativeCrypto.SSL_read方法的实现
        // ssl: 指向SSL对象的指针,表示当前的SSL连接
        // ssl_holder: SSL对象的持有者,用于管理SSL状态
        // fd: 文件描述符,表示底层的网络套接字
        // shc: SSL握手上下文,用于管理SSL握手过程
        // b: 字节数组,表示存储读取数据的缓冲区
        // off: 数据在字节数组中的起始偏移量
        // len: 要读取的数据长度
        // timeout: 读取操作的超时时间

        var res = this.SSL_read(ssl, ssl_holder, fd, shc, b, off, len, timeout); // 调用原始的SSL_read方法并获取返回值(实际读取的字节数)

        //console.log(HexDump.dumpHexString(b, off, len), "\n") // 打印字节数组的十六进制表示(注释掉)
        console.log(ByteString.of(b).utf8(), "\n"); // 将字节数组转换为UTF-8字符串并打印
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); // 打印当前堆栈信息(注释掉)
        return res; // 返回SSL_read方法的原始返回值
    };
});

// frida -UF -l 4.hook.js // 使用Frida命令行工具附加到目标应用并加载脚本
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); // 打印当前堆栈信息(注释掉)

// frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_Java_[SSL_read].js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd11_ssl02`. Resuming main thread!
// [MI 8::net.demo2.netd11_ssl02 ]-> HTTP/1.1 200 OK
// Date: Thu, 23 Jan 2025 14:40:11 GMT
// Content-Type: application/json
// Content-Length: 69
// Connection: keep-alive
// Alt-Svc: h3=":443"; ma=86400
// allow: GET, HEAD, OPTIONS
// vary: Cookie, Origin
// x-frame-options: DENY
// x-content-type-options: nosniff
// referrer-policy: same-origin
// Server: nginx
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Methods: *
// Access-Control-Allow-Headers: *
// Access-Control-Expose-Headers: Content-Length, Content-Range
// Access-Control-Allow-Credentials: true
// Access-Control-Max-Age: 1728000
//
// {"code":0,"data":{"count":0,"next":null,"previous":null,"result":[]}}
//
// java.lang.Throwable
//         at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method)
//         at com.android.org.conscrypt.NativeSsl.read(NativeSsl.java:411)
//         at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:549)
//         at net.demo2.netd11_ssl02.MainActivity$2.run(MainActivity.java:75)

 

so级别

 

上述的请求和写入,都是在java层面进行hook的获取,实际上真正的请求会调用so层的代码,最终将请求发送过去。

/*
 * Copyright (C) 2008 The Android Open Source Project
 */

package org.conscrypt;


@Internal
public final class NativeCrypto {
    // --- OpenSSL library initialization --------------------------------------

    /**
     * Reads with the native SSL_read function from the encrypted data stream
     * @return -1 if error or the end of the stream is reached.
     */
    static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc,
            byte[] b, int off, int len, int readTimeoutMillis) throws IOException;

    /**
     * Writes with the native SSL_write function to the encrypted data stream.
     */
    static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd,
            SSLHandshakeCallbacks shc, byte[] b, int off, int len, int writeTimeoutMillis)
            throws IOException;

}

 

 libssl.so

一般情况下底层是基于openssl将https请求发送出去,而openssl基于的是libssl.so的文件。

 

`SSL_write`和`SSL_read`

Java.perform(function () { // 使用Frida的Java.perform方法,确保代码在Java上下文中执行
    const apiResolver = new ApiResolver('module'); // 创建一个ApiResolver实例,用于解析模块中的符号(函数名)
    // apiResolver: 用于查找模块中的导出函数(如libssl.so中的SSL_write和SSL_read)

    // 包含:libttboringssl.so 或 libssl.so
    // 'exports:*lib*ssl*!SSL_*'
    // 匹配模式:查找所有导出函数名中包含"lib"、"ssl"和"SSL_"的函数
    apiResolver.enumerateMatches('exports:*lib*ssl*!SSL_*').forEach(function (v) { // 遍历所有匹配的符号
        // v: 包含符号信息的对象,包括函数名(v.name)和地址(v.address)

        if (v.name.indexOf('SSL_write') > 0) { // 如果函数名包含"SSL_write"
            // SSL_write = v.address; // 可以保存SSL_write函数的地址(注释掉)
            console.log(v.name); // 打印函数名
        } else if (v.name.indexOf('SSL_read') > 0) { // 如果函数名包含"SSL_read"
            // SSL_read = v.address; // 可以保存SSL_read函数的地址(注释掉)
            console.log(v.name); // 打印函数名
        }
    });
});

//  (.venv) calleng@tuxedo-os:~/p9/Mikrom2.0/season5/day36 抓包-服务端证书$ frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_[libssl.so]_OpenSSL_寻找SSL_write和SSL_read的hook的脚本.js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd11_ssl02`. Resuming main thread!
// [MI 8::net.demo2.netd11_ssl02 ]-> /apex/com.android.conscrypt/lib64/libssl.so!SSL_write
// /apex/com.android.conscrypt/lib64/libssl.so!SSL_read
// /apex/com.android.conscrypt/lib64/libssl.so!SSL_write
// /apex/com.android.conscrypt/lib64/libssl.so!SSL_read

SSL请求和响应的hook

frida -D 10.10.10.200:5555 -f net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_[libssl.so]_OpenSSL_得到请求和响应.js' // ____

 

Java.perform(function () { // 使用Frida的Java.perform方法,确保代码在Java上下文中执行
    const SSL_write = Module.getExportByName('libssl.so', 'SSL_write'); // 获取libssl.so中SSL_write函数的地址
    const SSL_read = Module.getExportByName('libssl.so', 'SSL_read'); // 获取libssl.so中SSL_read函数的地址

    Interceptor.attach(SSL_write, { // Hook SSL_write函数
        onEnter(args) { // 当SSL_write函数被调用时触发
            console.log('write call'); // 打印日志,表示SSL_write被调用
            //console.log(args[0]); // 打印第一个参数(ssl对象指针)
            //console.log(args[1]); // 打印第二个参数(数据缓冲区的指针)
            //console.log(args[2]); // 打印第三个参数(数据长度)
            //console.log(hexdump(args[1], {length: args[2].toInt32()})); // 打印缓冲区的十六进制数据(注释掉)
            console.log(Memory.readByteArray(args[1], parseInt(args[2]))); // 读取并打印缓冲区的字节数据
        }
    });

    Interceptor.attach(SSL_read, { // Hook SSL_read函数
        onEnter(args) { // 当SSL_read函数被调用时触发
            console.log('read call'); // 打印日志,表示SSL_read被调用
            this.buf = args[1]; // 保存第二个参数(数据缓冲区的指针)到this.buf
        },
        onLeave(retval) { // 当SSL_read函数返回时触发
            retval |= 0; // 将返回值转换为32位整数
            if (retval <= 0) { // 如果返回值小于等于0,表示读取失败或没有数据
                return; // 直接返回
            }
            console.log(Memory.readByteArray(this.buf, retval)); // 读取并打印缓冲区的字节数据
        }
    });
});

//  frida -D 10.10.10.200:5555  -f  net.demo2.netd11_ssl02 -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_底层_SSL_[libssl.so]_OpenSSL_得到请求和响应.js'
//      ____
//     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
//    | (_| |
//     > _  |   Commands:
//    /_/ |_|       help      -> Displays the help system
//    . . . .       object?   -> Display information about 'object'
//    . . . .       exit/quit -> Exit
//    . . . .
//    . . . .   More info at https://frida.re/docs/home/
//    . . . .
//    . . . .   Connected to MI 8 (id=10.10.10.200:5555)
// Spawned `net.demo2.netd11_ssl02`. Resuming main thread!
// [MI 8::net.demo2.netd11_ssl02 ]-> write call
//            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  47 45 54 20 2f 61 70 69 2f 76 31 2f 63 6f 75 72  GET /api/v1/cour
// 00000010  73 65 2f 61 63 74 75 61 6c 2f 3f 6c 69 6d 69 74  se/actual/?limit
// 00000020  3d 31 26 6f 66 66 73 65 74 3d 30 26 63 61 74 65  =1&offset=0&cate
// 00000030  67 6f 72 79 5f 69 64 3d 39 39 39 39 20 48 54 54  gory_id=9999 HTT
// 00000040  50 2f 31 2e 31 0d 0a 68 6f 73 74 3a 20 61 70 69  P/1.1..host: api
// 00000050  2e 6c 75 66 66 79 63 69 74 79 2e 63 6f 6d 0d 0a  .luffycity.com..
// 00000060  75 73 65 72 2d 41 67 65 6e 74 3a 20 74 65 73 74  user-Agent: test
// 00000070  0d 0a 0d 0a                                      ....
// read call
// read call
// read call
//            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d  HTTP/1.1 200 OK.
// 00000010  0a 44 61 74 65 3a 20 54 68 75 2c 20 32 33 20 4a  .Date: Thu, 23 J
// 00000020  61 6e 20 32 30 32 35 20 31 34 3a 35 35 3a 34 34  an 2025 14:55:44
// 00000030  20 47 4d 54 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79   GMT..Content-Ty
// 00000040  70 65 3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f  pe: application/
// 00000050  6a 73 6f 6e 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65  json..Content-Le
// 00000060  6e 67 74 68 3a 20 36 39 0d 0a 43 6f 6e 6e 65 63  ngth: 69..Connec
// 00000070  74 69 6f 6e 3a 20 6b 65 65 70 2d 61 6c 69 76 65  tion: keep-alive
// 00000080  0d 0a 41 6c 74 2d 53 76 63 3a 20 68 33 3d 22 3a  ..Alt-Svc: h3=":
// 00000090  34 34 33 22 3b 20 6d 61 3d 38 36 34 30 30 0d 0a  443"; ma=86400..
// 000000a0  61 6c 6c 6f 77 3a 20 47 45 54 2c 20 48 45 41 44  allow: GET, HEAD
// 000000b0  2c 20 4f 50 54 49 4f 4e 53 0d 0a 76 61 72 79 3a  , OPTIONS..vary:
// 000000c0  20 43 6f 6f 6b 69 65 2c 20 4f 72 69 67 69 6e 0d   Cookie, Origin.
// 000000d0  0a 78 2d 66 72 61 6d 65 2d 6f 70 74 69 6f 6e 73  .x-frame-options
// 000000e0  3a 20 44 45 4e 59 0d 0a 78 2d 63 6f 6e 74 65 6e  : DENY..x-conten
// 000000f0  74 2d 74 79 70 65 2d 6f 70 74 69 6f 6e 73 3a 20  t-type-options:
// 00000100  6e 6f 73 6e 69 66 66 0d 0a 72 65 66 65 72 72 65  nosniff..referre
// 00000110  72 2d 70 6f 6c 69 63 79 3a 20 73 61 6d 65 2d 6f  r-policy: same-o
// 00000120  72 69 67 69 6e 0d 0a 53 65 72 76 65 72 3a 20 6e  rigin..Server: n
// 00000130  67 69 6e 78 0d 0a 41 63 63 65 73 73 2d 43 6f 6e  ginx..Access-Con
// 00000140  74 72 6f 6c 2d 41 6c 6c 6f 77 2d 4f 72 69 67 69  trol-Allow-Origi
// 00000150  6e 3a 20 2a 0d 0a 41 63 63 65 73 73 2d 43 6f 6e  n: *..Access-Con
// 00000160  74 72 6f 6c 2d 41 6c 6c 6f 77 2d 4d 65 74 68 6f  trol-Allow-Metho
// 00000170  64 73 3a 20 2a 0d 0a 41 63 63 65 73 73 2d 43 6f  ds: *..Access-Co
// 00000180  6e 74 72 6f 6c 2d 41 6c 6c 6f 77 2d 48 65 61 64  ntrol-Allow-Head
// 00000190  65 72 73 3a 20 2a 0d 0a 41 63 63 65 73 73 2d 43  ers: *..Access-C
// 000001a0  6f 6e 74 72 6f 6c 2d 45 78 70 6f 73 65 2d 48 65  ontrol-Expose-He
// 000001b0  61 64 65 72 73 3a 20 43 6f 6e 74 65 6e 74 2d 4c  aders: Content-L
// 000001c0  65 6e 67 74 68 2c 20 43 6f 6e 74 65 6e 74 2d 52  ength, Content-R
// 000001d0  61 6e 67 65 0d 0a 41 63 63 65 73 73 2d 43 6f 6e  ange..Access-Con
// 000001e0  74 72 6f 6c 2d 41 6c 6c 6f 77 2d 43 72 65 64 65  trol-Allow-Crede
// 000001f0  6e 74 69 61 6c 73 3a 20 74 72 75 65 0d 0a 41 63  ntials: true..Ac
// 00000200  63 65 73 73 2d 43 6f 6e 74 72 6f 6c 2d 4d 61 78  cess-Control-Max
// 00000210  2d 41 67 65 3a 20 31 37 32 38 30 30 30 0d 0a 0d  -Age: 1728000...
// 00000220  0a 7b 22 63 6f 64 65 22 3a 30 2c 22 64 61 74 61  .{"code":0,"data
// 00000230  22 3a 7b 22 63 6f 75 6e 74 22 3a 30 2c 22 6e 65  ":{"count":0,"ne
// 00000240  78 74 22 3a 6e 75 6c 6c 2c 22 70 72 65 76 69 6f  xt":null,"previo
// 00000250  75 73 22 3a 6e 75 6c 6c 2c 22 72 65 73 75 6c 74  us":null,"result
// 00000260  22 3a 5b 5d 7d 7d                                ":[]}}
// read call

 

flutter的处理, 抓包

Quic 的抓包..