操作系统: Ubuntu 22.04.2 LTS
Linux 5.19.0-50-generic
x86_64
本文将介绍如何使用eBPF捕获使用LibreSSL和libTLS的HTTPS程序的明文数据。
与本文相关的信息如下:
-
eBPF, SSL/TLS
-
使用eBPF捕获HTTPS明文数据
-
使用eBPF捕获OpenSSL,GnuTLS,GoTLS明文数据
-
相关项目ecapture,pixie,kindling,skywalking,deepflow,Tracee…..
接下来,我们将介绍LibreSSL和LibreTLS,并使用eBPF捕获其明文数据。
00.LibreSSL
Intro
LibreSSL is a version of the TLS/crypto stack forked from OpenSSL in 2014, with goals of modernizing the codebase, improving security, and applying best practice development processes.
LibreSSL在其官网是如此介绍其自己的:LibreSSL是一个由OpenSSL fork出来的加密库,它致力于提供更简化、更安全的替代方案,专注于提供安全的传输层通信协议和加密算法。
2014年这一年,作为现代网络基础设施的OpenSSL库爆出心脏出血(heartbleed)漏洞,影响波及全球。而LibreSSL就是在此情况下对OpenSSL进行fork,旨在提供一个比 OpenSSL 更安全的替代品,从它那流血的心脏logo足以说明对安全的追求。
LibreSSL即Lib-re-SSL,也就重新实现SSL的意思,当看到Libre时不难让我们想到另外一个开源的库LibreOffice,而libreOffice也旨在替代OpenOffice。
How to trace
LibreSSL是OpenSSL的分支,并且是对OpenSSL保持兼容的,意味着在无需重新编译程序即可做到对OpenSSL的依赖。因此,对LibreSSL的明文数据捕获其实等同于OpenSSL的明文数据捕获,即对SSL_read和SSL_write函数数据的捕获。
因此,问题还是回归到了以下几个问题(pixie和skywalking-rover表示赞同):
-
需要{pid, fd}对SSL/TLS关联的socket进行标识;
-
fd可以通过ssl_st结构体得到,又因为在不同版本的LibreSSL其ssl_st等结构可能有所调整;
-
因此需要确定LibreSSL的版本信息,用于确定fd(ssl_st->rbio->num)的偏移值,以便在ebpf内核态中读取;
现在,下文将以LibreSSL 3.7.0作为实验版本进行介绍。
Version Info
下载LibreSSL源码并解压。
|
|
我们知道OpenSSL的中有宏OPENSSL_VERSION_NUMBER
和OPENSSL_VERSION_TEXT
用于识别对应的OpenSSL动态库的版本。
而对应的在LibreSSL中也有宏LIBRESSL_VERSION_NUMBER
和LIBRESSL_VERSION_TEXT
用于识别对应的版本信息, 见~/include/openssl/opensslv.h
文件。
|
|
另外,我们知道OpenSSL还提供了接口unsigned long OpenSSL_version_num(void);
和const char *OpenSSL_version(int type);
用于获取这两个宏,至于version_num是用一个unsigned long按特定的格式表示的版本号,具体可见OpenSSL文档。
在OpenSSL低版本(1.0)中,这两个接口为unsigned long SSLeay(void);
和const char *SSLeay_version(int type);
,在高版本中此二接口已经被上述接口替换。
而对于LibreSSL而言,由于其是从OpenSSL1.0拉取的分支,而为了保证兼容性同时保留了上述4个接口,可见~/include/openssl/crypto.h
。
|
|
值得注意的是,上面的接口位于libcrypto.so
而非libssl.so
:
|
|
此外,有一个奇怪的地方是OpenSSL似乎没有2.0版本,直接从OpenSSL 1.1跳到了OpenSSL 3.0,而在LibreSSL中我们又发现OPENSSL_VERSION_NUMBER
和SSLEAY_VERSION_NUMBER
的值永远都是0x20000000L
,换算一下就是2.0。似乎,LibreSSL就是OpenSSL2.0。
|
|
所以,在LibreSSL中通过SSLeay或OpenSSL_version_num返回的永远都是0x20000000L
是无法正确反应LibreSSL的版本信息的,因此更恰当的获取LibreSSL版本号是通过SSLeay_version或OpenSSL_version接口。
Offsets of rbio&num
版本号的问题解决了,下一个问题就是如何计算求得strcut ssl_st
中的rbio偏移位置和struct bio_st
中的num偏移位置。因此,我们可以通过如下代码在64bits的机器上获取到上述两个的值:
|
|
编译以上代码:
|
|
运行可得:
|
|
Trace with eBPF
通过研究pixie,tracee,skywalking等网络观测开源项目得eBPF模块可知(其实kern模块大家写得基本上都差不多)。使用eBPF进行HTTPS网络观测是的通常的可以分以下步骤:
-
用户态
-
输入为pid时,通过文件
/proc/{pid}/maps
查找进程依赖的libssl.so和libcrypto.so路径; -
在libcrypto.so中得到OpenSSL/LibreSSL等的版本号信息,根据版本号确定BIO和num的偏移值;
-
通过bpf map将偏移值以{key: pid, value: offset}的方式传递到bpf内核态中以供使用;
-
通过
perf_buffer__poll()
轮询内核上来的数据。
-
-
内核态
-
attach 对应的uprobe函数即SSL_write/SSL_read,分为entry和return两部分;
-
在entry时,将数据以{key: pid+fd, value: data}的方式存储在bpf map中,其中fd通过用户传递的offset,从ssl_st中获取;
-
在return时,通过key:pid+fd从bpf map中取得数据,并输出到perf_event中以供用户态读取。
-
Kern Space
|
|
User Space
|
|
Run
以mongoose-7.11/examples/device-dashboard/
为测试样例,
以root运行:
|
|
|
|
01.LibreTLS
Intro
LibreTLS
is a port of libtls from LibreSSL to OpenSSL. libtls is “a new TLS library, designed to make it easier to write foolproof applications”.libtls provides an excellent new API, but LibreSSL can be difficult to install on systems which already use OpenSSL.
LibreTLS
aims to make the libtls API more easily and widely available.
LibreTLS是LibreSSL项目的一部分,它提供了一个简单的TLS库,旨在为应用程序开发人员提供易于使用的TLS/SSL功能。LibreTLS专注于简化TLS/SSL的实现,使其更容易集成到各种应用程序中。
可以单独获取LibreTLS的源码进行编译,亦可以通过编译LibreSSL获得,毕竟LibreSSL项目源码中已经包含了LibreTLS。
|
|
How to trace
原理和OpenSSL、LibreSSL一样,只不过这里我们跟踪的是tls_read和tls_write这两个函数。
|
|
而翻阅libretls/tls.c#line919~
可以发现,其实tls_read和tls_write其中最终还是调用了SSL_read和SSL_write,所以可以假设:并不需要在跟踪libretls了,因为跟踪libretls的本质最终还是在跟踪libssl。
但是,实际情况并没有假设的这么简单,在对比查看libretls和libressl的tls目录后,我发现libretls虽然依赖了libssl,但是由于libretls本质上是libressl的一部分,因此就有如下可能:
-
libretls在编译的时候直接将libssl和libcrypto的源码编译到.so中,这时候export的函数是没有SSL_开头的(LOCAL),因而还是需要跟踪tls_read和tls_write。
-
libretls可以动态依赖libssl和libcrypto,这时候对libretls的跟踪实际上就是对libssl的跟踪。
对于可能2,问题回归到了对libssl即还是对OpenSSL或LibreSSL的跟踪,在此无需赘述,接下来的内容主要聚焦于如何解决可能1。
Version Info
同上,版本号的获取还是通过libcrypto.so获取。
Offset of socket
|
|
Trace with eBPF
Kern Space
|
|
User Space
同LibreSSL#User Space.
02.The end
在本文中,我们介绍了通过eBPF技术捕获LibreSSL的方法。更上一层楼,我非常推荐继续进阶阅读Pixie、Tracee、ecapture、skywalking和Deepflow等项目的eBPF模块代码,虽然基本上的思路大差不差的,但作为前驱者,它们的贡献是巨大的。
03.Links
https://github.com/pinkkmin/blog-code/trace_libressl
Index of /pub/OpenBSD/LibreSSL/
https://git.causal.agency/libretls/about/
/docs/manmaster/man3/OPENSSL_VERSION_NUMBER.html
04.Others
Script: Get LibreSSL offset.
|
|
Bye Bye ~