Featured image of post eBPF:Trace LibreSSL/LibreTLS with eBPF

eBPF:Trace LibreSSL/LibreTLS with eBPF

使用eBPF捕获使用LibreSSL和libTLS的HTTPS程序的明文数据。

操作系统:   Ubuntu 22.04.2 LTS

                    Linux 5.19.0-50-generic

                    x86_64

代码地址: https://github.com/pinkkmin/blog-code/trace_libressl

本文将介绍如何使用eBPF捕获使用LibreSSL和libTLS的HTTPS程序的明文数据。

与本文相关的信息如下:

  • eBPF, SSL/TLS

  • 使用eBPF捕获HTTPS明文数据

  • 使用eBPF捕获OpenSSL,GnuTLS,GoTLS明文数据

  • 相关项目ecapture,pixie,kindling,skywalking,deepflow,Tracee…..

接下来,我们将介绍LibreSSL和LibreTLS,并使用eBPF捕获其明文数据。

00.LibreSSL

https://www.libressl.org

Index of /pub/OpenBSD/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源码并解压。

1
wget https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-3.7.0.tar.gz

我们知道OpenSSL的中有宏OPENSSL_VERSION_NUMBEROPENSSL_VERSION_TEXT用于识别对应的OpenSSL动态库的版本。

而对应的在LibreSSL中也有宏LIBRESSL_VERSION_NUMBERLIBRESSL_VERSION_TEXT用于识别对应的版本信息, 见~/include/openssl/opensslv.h文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ~/include/openssl/opensslv.h

/* These will change with each release of LibreSSL-portable */
#define LIBRESSL_VERSION_NUMBER 0x3070000fL
/*                                    ^ Patch starts here   */
#define LIBRESSL_VERSION_TEXT   "LibreSSL 3.7.0"

/* These will never change */
#define OPENSSL_VERSION_NUMBER    0x20000000L
#define OPENSSL_VERSION_TEXT    LIBRESSL_VERSION_TEXT
#define OPENSSL_VERSION_PTEXT    " part of " OPENSSL_VERSION_TEXT

另外,我们知道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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ~/include/openssl/crypto.h line 319 ~ 329
const char *OpenSSL_version(int type);
#define OPENSSL_VERSION        0
#define OPENSSL_CFLAGS        1
#define OPENSSL_BUILT_ON    2
#define OPENSSL_PLATFORM    3
#define OPENSSL_DIR        4
#define OPENSSL_ENGINES_DIR    5
unsigned long OpenSSL_version_num(void);

const char *SSLeay_version(int type);
unsigned long SSLeay(void);

值得注意的是,上面的接口位于libcrypto.so而非libssl.so

1
2
3
readelf -sW ./crypto/.libs/libcrypto.so|grep SSLeay_version
4106: 00000000000901a0   120 FUNC    GLOBAL DEFAULT   12 SSLeay_version
4274: 00000000000901a0   120 FUNC    GLOBAL DEFAULT   12 SSLeay_version

此外,有一个奇怪的地方是OpenSSL似乎没有2.0版本,直接从OpenSSL 1.1跳到了OpenSSL 3.0,而在LibreSSL中我们又发现OPENSSL_VERSION_NUMBERSSLEAY_VERSION_NUMBER的值永远都是0x20000000L,换算一下就是2.0。似乎,LibreSSL就是OpenSSL2.0。

1
2
// ~/include/openssl/crypto.h line 138
#define SSLEAY_VERSION_NUMBER    OPENSSL_VERSION_NUMBER

所以,在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的机器上获取到上述两个的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stddef.h>
#include "openssl/crypto.h"
#include "openssl/bio.h"

#if defined(BIO_LOCL_H)
#include "crypto/bio/bio_local.h"
#endif

#if defined(SSL_LOCL_H)
#include "ssl/ssl_locl.h"
#else
#include "ssl/ssl_local.h"
#endif

int main(int argc, char **argv)
{
    printf("#define LibreSSL_RBIO_offset 0x%lx \n", 
        offsetof(struct ssl_st, rbio));
    printf("#define LibreSSLRBIO_num_offset  0x%lx \n", 
        offsetof(struct bio_st, num));
    return 0;
}

编译以上代码:

1
2
3
4
5
6
# VERSION > 3.4.3
gcc -DSSL_LOCL_H -D__BEGIN_HIDDEN_DECLS= -D__END_HIDDEN_DECLS= -I include/ -I . offset.c -o offset
# 3.4.3 < VERISON <= 3.6.3
gcc -DSSL_LOCL_H -DBIO_LOCL_H -D__BEGIN_HIDDEN_DECLS= -D__END_HIDDEN_DECLS= -I include/ -I . offset.c -o offset
# VERSION > 3.6.3
gcc -DBIO_LOCL_H -D__BEGIN_HIDDEN_DECLS= -D__END_HIDDEN_DECLS= -I include/ -I . offset.c -o offset

运行可得:

1
2
#define LibreSSL_RBIO_offset 0x18 
#define LibreSSL_RBIO_num_offset  0x30

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/***********************************************************
 * BPF probe function entry-points
 ***********************************************************/

//  int SSL_read(SSL *ssl, void *buf, int num);
SEC("uprobe/SSL_read")
int probe_entry_ssl_read(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();

    void* ssl = (void*)PT_REGS_PARM1(ctx);
    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    // int fd = get_ssl_fd(ssl);
    // if(fd == kInvalidFd) {
    //     bpf_printk("probe_entry_ssl_read: get fd fail.");
    //     return 0;
    // }

    struct data_args_t read_args = {};
    read_args.buf = buf;
    // read_args.fd = fd;
    bpf_map_update_elem(&active_read_args_maps, &id, &read_args, BPF_ANY);

    return 0;
}

SEC("uretprobe/SSL_read")
int probe_ret_ssl_read(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();
    struct data_args_t* read_args = bpf_map_lookup_elem(&active_read_args_maps, &id);
    if(read_args != NULL) {
        process_ssl_data(ctx, kSSL_Read, read_args);
    }

    bpf_map_delete_elem(&active_read_args_maps, &id);
    return 0;
}


SEC("uprobe/SSL_write")
int probe_entry_ssl_write(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();

    void* ssl = (void*)PT_REGS_PARM1(ctx);
    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    // int fd = get_ssl_fd(ssl);
    // if(fd == kInvalidFd) {
    //     bpf_printk("probe_entry_ssl_write: get fd fail.");
    //     return 0;
    // }

    struct data_args_t write_args = {};
    write_args.buf = buf;
    // write_args.fd = fd;
    bpf_map_update_elem(&active_write_args_maps, &id, &write_args, BPF_ANY);

    return 0;
}

SEC("uretprobe/SSL_write")
int probe_ret_ssl_write(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();
    struct data_args_t* write_args = bpf_map_lookup_elem(&active_write_args_maps, &id);
    if(write_args != NULL) {
        process_ssl_data(ctx, kSSL_Write, write_args);
    }

    bpf_map_delete_elem(&active_write_args_maps, &id);
    return 0;
}

User Space

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/***********************************************************
 * Skeleton helper functions
 ***********************************************************/
struct bpf_prog_skeleton* get_bpf_program_by_probename(const struct trace_libressl_bpf * obj, 
    const std::string &name) {
    if(!obj || !obj->skeleton) return NULL;
    struct bpf_object_skeleton *skeleton = obj->skeleton;

    int prog_cnt = skeleton->prog_cnt;
    for(int i = 0; i < prog_cnt; i++) {
        if(0 == strcmp(name.c_str(), skeleton->progs[i].name)) 
            return skeleton->progs + i;
    }

    return NULL;
}

bool attach_uprobe(const struct trace_libressl_bpf * obj, const struct uprobe_t &uprobe) {
    struct bpf_prog_skeleton* prog_skeleton = get_bpf_program_by_probename(obj, uprobe.probe_fn);
    if(!prog_skeleton) return false;

    DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, opts, 
            .retprobe=uprobe.ret,
            .func_name=uprobe.symbol.c_str());

    *(prog_skeleton->link) = bpf_program__attach_uprobe_opts(*(prog_skeleton->prog),
        uprobe.trace_pid, 
        uprobe.binary.c_str(), 
        uprobe.offset, 
        &opts);

    int err = libbpf_get_error(*(prog_skeleton->link));
    if(0!=err) {
        char errmsg[256]{};
        libbpf_strerror(err, errmsg, sizeof(errmsg));
        printf("[Error] %s\n", errmsg);
        return false;
    }

    return true;
}

const std::vector<uprobe_t> kLibreSSLProbes = {
    {
        .ret = false,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "SSL_read",
        .probe_fn = "probe_entry_ssl_read"
    },
    {
        .ret = true,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "SSL_read",
        .probe_fn = "probe_ret_ssl_read"
    },
    {
        .ret = false,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "SSL_write",
        .probe_fn = "probe_entry_ssl_write"
    },
    {
        .ret = true,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "SSL_write",
        .probe_fn = "probe_ret_ssl_write"
    }
};

const std::vector<uprobe_t> kLibreTLSProbes = {
    {
        .ret = false,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "tls_read",
        .probe_fn = "probe_entry_tls_read"
    },
    {
        .ret = true,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "tls_read",
        .probe_fn = "probe_ret_tls_read"
    },
    {
        .ret = false,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "tls_write",
        .probe_fn = "probe_entry_tls_write"
    },
    {
        .ret = true,
        .offset = 0,
        .trace_pid = -1,
        .binary = "",
        .symbol = "tls_write",
        .probe_fn = "probe_ret_tls_write"
    }
};

DEFINE_string(libretls, 
              "", 
              "libretls path, such as `/usr/local/lib/libtls.so.28`");

DEFINE_string(libressl, 
              "", 
              "libressl path, such as `/usr/local/lib/libssl.so.53`");

struct params_t {
    enum type_t {
        LibreSSL,
        LibreTLS
    };
    enum type_t type;
    std::string path;    
};

std::vector<struct params_t> check_params() {
    std::vector<struct params_t> params;

    if(access(FLAGS_libressl.c_str(), F_OK) == 0) {
       struct params_t prm;
       prm.type = params_t::LibreSSL;
       prm.path = FLAGS_libressl;
       params.push_back(prm);
    }

    if(access(FLAGS_libretls.c_str(), F_OK) == 0) {
       struct params_t prm;
       prm.type = params_t::LibreTLS;
       prm.path = FLAGS_libretls;
       params.push_back(prm);
    }

    return params;
}

int main(int argc, char** argv) {
    ::gflags::ParseCommandLineFlags(&argc, &argv, true);
    std::vector<struct params_t> params = check_params();
    if(params.size() == 0) {
        printf("[trace-libretls] usage: \n");
        printf("--libretls /usr/local/lib/libtls.so.28 \n");
        printf("--libressl /usr/local/lib/libssl.so.53 \n");
        return -1;
    }

    // BPF object and perf buffer
    struct trace_libressl_bpf * obj;
    struct perf_buffer* pb = NULL;

    libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
    libbpf_set_print(libbpf_print_fn);

    // Open
    LIBBPF_OPTS(bpf_object_open_opts, open_opts);
    obj = trace_libressl_bpf__open_opts(&open_opts);
    if (!obj) {
        printf("[trace-libretls] Fail to open BPF object \n");
        return 1;
    }

    // Load
    int err = trace_libressl_bpf__load(obj);
    if (err) {
        printf("[trace-libretls] Failed to load BPF object: %d\n", err);
        goto cleanup;
    }

    // Open perf event buffer
    pb = perf_buffer__new(bpf_map__fd(obj->maps.ssl_events), 
        PERF_BUFFER_PAGES,
        handle_event, 
        NULL, NULL, NULL);

    if (!pb) {
        int err = -errno;
        printf("failed to open perf buffer: %d\n", err);
        goto cleanup;
    }

    // Attach LibreSSL and LIbreTLS
    for(const auto &param: params) {
        const std::vector<uprobe_t> *probes = nullptr;
        if(param.type == params_t::LibreSSL) {
            probes = &kLibreSSLProbes; 
        }
        else {
            probes = &kLibreTLSProbes; 
        }

        for(const auto &u: *probes) {
            struct uprobe_t uprobe = u;
            uprobe.binary = param.path;
            attach_uprobe(obj, uprobe);
        }
    }

     // Poll data
    while (true) {
        err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
        if (err < 0 && err != -EINTR) {
            printf("error polling perf buffer: %s\n", strerror(-err));
            goto cleanup;
        }
        err = 0;
    }

cleanup:
    if(obj) {
        if(pb) perf_buffer__free(pb);
        trace_libressl_bpf__destroy(obj);
    }
    return err != 0;
}

// run with root privilege.
// ./trace_libressl /usr/local/lib/libssl.so.53

Run

mongoose-7.11/examples/device-dashboard/为测试样例,

以root运行:

1
sudo ./trace_libressl --libressl /usr/local/lib/libssl.so.53
1
2
# curl 请求
curl -k https://localhost:8443

01.LibreTLS

https://git.causal.agency/libretls/about/

https://causal.agency/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。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
find ./ -name libtls.so*
./.libs/libtls.so.28.0.0
./.libs/libtls.so.28
./.libs/libtls.so

ldd .libs/libtls.so 
linux-vdso.so.1 (0x00007ffcaa9ef000)
libssl.so.55 => not found
libcrypto.so.52 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9ea5400000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9ea56b2000)

How to trace

原理和OpenSSL、LibreSSL一样,只不过这里我们跟踪的是tls_read和tls_write这两个函数。

1
2
3
// include/tls.h
ssize_t tls_read(struct tls *_ctx, void *_buf, size_t _buflen);
ssize_t tls_write(struct tls *_ctx, const void *_buf, size_t _buflen);

而翻阅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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct tls {
    struct tls_config *config;
    struct tls_keypair *keypair;

    struct tls_error error;

    uint32_t flags;
    uint32_t state;

    char *servername;
    int socket;

    SSL *ssl_conn;
    SSL_CTX *ssl_ctx;

    struct tls_sni_ctx *sni_ctx;

    X509 *ssl_peer_cert;
    STACK_OF(X509) *ssl_peer_chain;

    struct tls_conninfo *conninfo;

    struct tls_ocsp *ocsp;

    tls_read_cb read_cb;
    tls_write_cb write_cb;
    void *cb_arg;
};

Trace with eBPF

Kern Space

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// ssize_t tls_write(struct tls *_ctx, const void *_buf, size_t _buflen);
SEC("uprobe/tls_write")
int probe_entry_tls_write(struct pt_regs *ctx) {
    void* tls_ctx = (void*)PT_REGS_PARM1(ctx);
    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    int fd = get_tls_fd(tls_ctx);
    if(fd == kInvalidFd) {
        bpf_printk("probe_entry_tls_write: get fd fail.");
        return 0;
    }

    struct data_args_t write_args = {};
    write_args.buf = buf;
    write_args.fd = fd;

    uint64_t id = bpf_get_current_pid_tgid();
    bpf_map_update_elem(&active_write_args_maps, &id, &write_args, BPF_ANY);

    return 0;
}

SEC("uretprobe/tls_write")
int probe_ret_tls_write(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();
    struct data_args_t* write_args = bpf_map_lookup_elem(&active_write_args_maps, &id);
    if(write_args != NULL) {
        process_ssl_data(ctx, kSSL_Write, write_args);
    }

    bpf_map_delete_elem(&active_write_args_maps, &id);
    return 0;
}

// ssize_t tls_read(struct tls *_ctx, void *_buf, size_t _buflen);
SEC("uprobe/tls_read")
int probe_entry_tls_read(struct pt_regs *ctx) {
    void* tls_ctx = (void*)PT_REGS_PARM1(ctx);
    const char* buf = (const char*)PT_REGS_PARM2(ctx);
    int fd = get_tls_fd(tls_ctx);
    if(fd == kInvalidFd) {
        bpf_printk("probe_entry_tls_write: get fd fail.");
        return 0;
    }

    struct data_args_t read_args = {};
    read_args.buf = buf;
    read_args.fd = fd;

    uint64_t id = bpf_get_current_pid_tgid();
    bpf_map_update_elem(&active_read_args_maps, &id, &read_args, BPF_ANY);

    return 0;
}

SEC("uretprobe/tls_read")
int probe_ret_tls_read(struct pt_regs *ctx) {
    uint64_t id = bpf_get_current_pid_tgid();
    struct data_args_t* read_args = bpf_map_lookup_elem(&active_read_args_maps, &id);
    if(read_args != NULL) {
        process_ssl_data(ctx, kSSL_Read, read_args);
    }

    bpf_map_delete_elem(&active_read_args_maps, &id);
    return 0;
}

User Space

同LibreSSL#User Space.

02.The end

在本文中,我们介绍了通过eBPF技术捕获LibreSSL的方法。更上一层楼,我非常推荐继续进阶阅读Pixie、Tracee、ecapture、skywalking和Deepflow等项目的eBPF模块代码,虽然基本上的思路大差不差的,但作为前驱者,它们的贡献是巨大的。

https://github.com/pinkkmin/blog-code/trace_libressl

https://www.libressl.org

Index of /pub/OpenBSD/LibreSSL/

https://git.causal.agency/libretls/about/

Index of /libretls/

/docs/manmaster/man3/OPENSSL_VERSION_NUMBER.html

04.Others

Script: Get LibreSSL offset.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/bin/bash

LibreSSL_VERSION=$1
LibreSSL_DIR="libressl-$LibreSSL_VERSION"
PROJECT_DIR=$(pwd)
URL_PREFIX="https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/"

function download_libressl() {
    if [ ! -d "$LibreSSL_DIR" ]; then
        filename="libressl-$LibreSSL_VERSION.tar.gz"
        if [ ! -f "$filename" ]; then
            wget "$URL_PREFIX/$filename"
        fi
        tar -xvf $filename
    fi

    if [ ! -d "$LibreSSL_DIR" ]; then
        exit 0
    fi
}

function make_offset() {
    exec_file=../offset_$LibreSSL_VERSION
    header_file="../libressl_offset.h"

    cd $PROJECT_DIR/$LibreSSL_DIR/
    ./config
    cat ../offset.c >> offset.c

    flags=""
    compare_result=$(echo -e "$LibreSSL_VERSION\n3.4.3" | sort -rV | head -n 1)
    if [ "$compare_result" != "3.4.3" ]; then
        flags="-DBIO_LOCL_H"
    fi

    compare_result=$(echo -e "$LibreSSL_VERSION\n3.6.3" | sort -rV | head -n 1)
    if [ "$compare_result" == "3.6.3" ]; then
        flags="$flags -DSSL_LOCL_H -D__BEGIN_HIDDEN_DECLS= -D__END_HIDDEN_DECLS= -DLIBRESSL_INTERNAL"
    else
        flags="$flags -D__BEGIN_HIDDEN_DECLS= -D__END_HIDDEN_DECLS="
    fi

    gcc ${flags} -I ./include/ -I . offset.c -o $exec_file
    rm -rf offset.c
    if [ -f "$exec_file" ]; then
        echo -e "\n// *************************************************" >> $header_file
        echo "// LibreSSL VERSION:"$LibreSSL_VERSION"  OFFSET            " >> $header_file
        echo "// *************************************************" >> $header_file
        output_result=`$exec_file $(echo $LibreSSL_VERSION | sed 's/\./_/g') `

        echo -e ""
        echo "******************************************************************************************"
        echo "EXECUTE: "$exec_file $(echo $LibreSSL_VERSION | sed 's/\./_/g') ">>" $header_file
        printf "%s\n" "$output_result"
        printf "%s\n" "$output_result" >> $header_file
        echo "******************************************************************************************"
        echo -e ""
        rm -rf $exec_file
    fi 
    cd ../..
}

# RUN
if [[ "$LibreSSL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+[a-z]?$ ]]; then
    echo "*******************************"
    echo "LibreSSL VERSION: "$1
    echo "*******************************"

    # download 
    download_libressl
    # make
    make_offset
else
    echo "please input the version number (major.minor.patch), such as 3.8.0"
fi

Bye Bye ~

哦吼是一首歌。
Built with Hugo
Theme Stack designed by Jimmy