JNI层SSL通信抓包与溯源
在一些场景下对App与服务端交互的数据安全程度要求较高,可能会对通信进行加密传输
- 使用HTTPS/TLS:使用HTTPS协议,在传输层使用TLS进行加密,确保数据在传输过程中不会被窃取或篡改。
- SSL Pinning:通过SSL Pinning确保客户端只信任特定的服务器证书,防止中间人攻击。
在HTTPS/SSL通信的过程当中可以使用Java层的API完成与服务器的通信,也有可能在JNI层直接使用openssl/boringSSL(在JNI层当中去使用SSL通信与服务器建立一个安全的加密通道来完成数据的发送)。
之前我们分析了在Java层的SSL,现在这篇文章来分析一下JNI层的SSL
要完成对JNI层SSL通信的抓包和溯源,需要对NativeCryptoClass.SSL_read
、还有NativeCryptoClass.SSL_write
这两个Java层函数继续往下分析进入到libc
当中的系统调用(这里以11.0的源码为例)
源码分析
继续往下寻找这个JNI函数的实现,他完整的名字就是NativeCrypto_SSL_write
、NativeCrypto_SSL_read
。
这两个函数所在的文件位置都是一样的,接下来我们以NativeCrypto_SSL_write
为例分析
将
jbyteArray b
转成了jbyte buf[1024]
由sslWrite
进行循环发送接下来我们需要搞明白
NativeCrypto_SSL_write
是如何调用BoringSSL把App的数据进行加密,然后进行发送的
继续分析这个sslWrite
函数
- 调用了boringSSL库中的
SSL_write
发送buf
从boringssl 和源码 中都可以找到SSL_write
,这里注意大小写
- 继续往下分析
write_app_data
- 这里不能直接搜索
write_app_data
,需要在前面加一个前缀ssl3,仅限于当前版本,在之后版本的源码中可能会变
- 那么继续分析
do_ssl3_write
,第三个参数是要发送的数据
- 到了这里之后,看到了参数中开始有cipher相关的关键字。特别关注一下这个函数,可能再往下就开始对数据进行加密了
- 继续往下跟踪
ssl3_write_pending
in
参数是指向待发送数据的指针,但在ssl3_write_pending
函数中并没有直接发送in
指向的数据。实际上,ssl3_write_pending
函数主要用于处理之前挂起的写操作,并检查当前的写操作是否与之前的挂起操作兼容。数据的实际发送是通过
ssl_write_buffer_flush
函数完成的。ssl_write_buffer_flush
会处理实际的数据发送操作,将挂起的数据通过底层的网络套接字发送出去。继续往下跟踪
ssl_write_buffer_flush
- 根据ssl版本的不同调用不同的API
- 这两个API都是调用了
BIO_write
发送数据
继续跟踪
bwrite
这里继续往下跟踪较为麻烦一些,先跟过去
BIO_METHOD
结构体定义了很多函数指针来执行各种BIO
操作,这里我们只关注bwrite
- 再往上翻翻其它定义,找到了
BIO_s_socket
方法的声明
- 接下来,跟进去找到
BIO_s_socket
的实现
- 这里在socket.c文件中,定义了一个具体的
BIO_METHOD
结构体实例methods_sockp
,并将bwrite
指向BIO_METHOD
结构体的第一个成员sock_write
函数,sock_read
对应的自然是第二个成员bread
用IDA分析libc.so文件,我们也能找到
write
的系统调用号
这里根据跟踪到的结果我们就可以知道实际的调用流程:
当调用
BIO_write
时,实际调用的是bio->method->bwrite
,即sock_write
函数
在 sock_write
函数中不同的平台调用了不同的函数来完成数据发送。WINDOWS平台使用send
其它平时使用write
总结:所以此时我们可以知道在boringssl中并没有使用libc中的sendto
来进行加密数据的发送,而是使用的write
。读取使用的是read
,同样是在socket.c文件中这里不再展开赘述。所以我们hook libc中的sendto
、recvfrom
是无法获取到HTTPS/SSL的数据包的
编写脚本
经过我们对源码的分析发现SSL加密的分界线在do_ssl3_write
之后
sslWrite、SSL_write、ssl3_write_app_data、do_ssl3_write
明文密文分水岭
ssl3_write_pending、BIO_write、sock_write、write
获取明文的数据就需要hook do_ssl3_write
之前的函数
这里我们以SSL_write
为hook目标编写脚本获取SSL通信的明文数据
function LogPrint(log) {
var theDate = new Date();
var time = theDate.toISOString().split('T')[1].replace('Z', '');
var threadid = Process.getCurrentThreadId();
console.log(`[${time}] -> threadid:${threadid} -- ${log}`);
}
function printNativeStack(context, name) {
var array = Thread.backtrace(context, Backtracer.ACCURATE);
var first = DebugSymbol.fromAddress(array[0]);
if (first.toString().indexOf('libopenjdk.so!NET_Send') < 0) {
var trace = array.map(DebugSymbol.fromAddress).join("\n");
LogPrint(`-----------start:${name}--------------`);
LogPrint(trace);
LogPrint(`-----------end:${name}--------------`);
}
}
function hooklibssl() {
var libsslmodule = Process.getModuleByName("libssl.so");
var SSL_read_addr = libsslmodule.getExportByName("SSL_read");
var SSL_write_addr = libsslmodule.getExportByName("SSL_write");
console.log(SSL_read_addr + "---" + SSL_write_addr);
// SSL_write(SSL *ssl, const void *buf, int num)
Interceptor.attach(SSL_read_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
LogPrint("go into libssl.so->SSL_read");
printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_read");
},
onLeave(retval) {
var size = retval.toInt32();
if (size > 0) {
console.log(Process.getCurrentThreadId() + "---libssl.so->SSL_read:" + hexdump(this.arg1, {
length: size
}));
}
LogPrint("leave libc.so->SSL_read");
},
});
// int SSL_read(SSL *ssl, void *buf, int num)
Interceptor.attach(SSL_write_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
LogPrint("go into libssl.so->SSL_write");
printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_write");
},
onLeave(retval) {
var size = ptr(this.arg2).toInt32();
if (size > 0) {
console.log(Process.getCurrentThreadId() + "---libssl.so->SSL_write:" + hexdump(this.arg1, {
length: size
}));
}
LogPrint("leave libssl.so->SSL_write");
},
});
}
function main() {
hooklibssl();
}
setImmediate(main);
测试脚本
这里我们以腾讯新闻App的登陆功能为例:
执行命令:可能抓取到明文的手机号码
frida -UF -l HOpenssl.js -o TXHO.log
直接使用抓包工具无法抓到SSL的明文数据
- 标题: JNI层SSL通信抓包与溯源
- 作者: xiaoeryu
- 创建于 : 2024-07-04 20:50:51
- 更新于 : 2024-07-05 11:51:17
- 链接: https://github.com/xiaoeryu/2024/07/04/JNI层SSL通信抓包与溯源/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。