eCapture 无侵入式卸载TLS加密报文

268 阅读2分钟

eCapture 介绍

(github.com/gojue/ecapt…)

用法介绍

  1. 编写一个golang的简单http客户端,请求https资源
package main
import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {

    b, e := GetHttp("https://github.com")
    if e == nil {
        fmt.Printf("response body: %s\n\n", b)
    } else {
        fmt.Printf("error :%v", e)
    }
}

func GetHttp(url string) (body []byte, err error) {
  // 开启TLS密钥记录,用于跟eCpature捕获的密钥对比。
    f, err := os.OpenFile("/tmp/go_master_secret.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
    if err != nil {
        panic(err)
    }
    defer f.Close()
    c := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true, KeyLogWriter: f},
        }}
    resp, e := c.Get(url)
    if e != nil {
        return nil, e
    }

    defer resp.Body.Close()
    body, err = io.ReadAll(resp.Body)
    return body, err
}
  
  1. 编译成可自行文件 main
    go build -o main main.go
  2. 分别执行ecapture程序和main程序

    /home/wisteria/tls-decrypt/main
    ./ecapture gotls -e=/home/wisteria/tls-decrypt/main -m pcap -w a2.pcap -i eth0

  3. 用wireshark查看保存的a2.pcap文件,可以看到解密后的报文

1726724620494.png

gotls卸载分析

  • ebpf uprobe
    核心原理是利用ebpf uprobe技术 hook 到ssl相关的函数,下面以writeKeyLog函数为例

    • 用户态程序注册函数
    const (
    GoTlsReadFunc         = "crypto/tls.(*Conn).Read"
    GoTlsWriteFunc        = "crypto/tls.(*Conn).writeRecordLocked"
    GoTlsMasterSecretFunc = "crypto/tls.(*Config).writeKeyLog"
    )
    
    if g.isRegisterABI {
            sec = "uprobe/gotls_mastersecret_register"
            fn = "gotls_mastersecret_register"
    } else {
            sec = "uprobe/gotls_mastersecret_stack"
            fn = "gotls_mastersecret_stack"
    }
    
    g.bpfManager = &manager.Manager{
            Probes: []*manager.Probe{
                    {
                            Section:          "classifier/egress",
                            EbpfFuncName:     tcFuncNameEgress,
                            Ifname:           g.ifName,
                            NetworkDirection: manager.Egress,
                    },
                    {
                            Section:          "classifier/ingress",
                            EbpfFuncName:     tcFuncNameIngress,
                            Ifname:           g.ifName,
                            NetworkDirection: manager.Ingress,
                    },
                    // --------------------------------------------------
    
                    // gotls master secrets
                    {
                            Section:          sec,
                            EbpfFuncName:     fn,
                            AttachToFuncName: config.GoTlsMasterSecretFunc,
                            BinaryPath:       g.path,
                            UID:              "uprobe_gotls_master_secret",
                            UAddress:         g.conf.(*config.GoTLSConfig).GoTlsMasterSecretAddr,
                    },
            },
    
    • 内核ebpf挂载程序
    SEC("uprobe/gotls_mastersecret_register")
    int gotls_mastersecret_register(struct pt_regs *ctx) {
        return gotls_mastersecret(ctx, true);
    }
    
     * crypto/tls/common.go
     * func (c *Config) writeKeyLog(label string, clientRandom, secret []byte) error
     */
    static __always_inline int gotls_mastersecret(struct pt_regs *ctx,
                                                  bool is_register_abi) {
        //    const char *label, *clientrandom, *secret;
        void *lab_ptr, *cr_ptr, *secret_ptr;
        void *lab_len_ptr, *cr_len_ptr, *secret_len_ptr;
        s32 lab_len, cr_len, secret_len;
    
        /*
         *
         * in golang struct, slice header like this
         * type slice struct {
         * 	array unsafe.Pointer
         * 	len   int
         * 	cap   int
         * }
         * so, arument index are in the order one by one
         *
         */
        lab_ptr = (void *)go_get_argument(ctx, is_register_abi, 2);
        lab_len_ptr = (void *)go_get_argument(ctx, is_register_abi, 3);
        cr_ptr = (void *)go_get_argument(ctx, is_register_abi, 4);
        cr_len_ptr = (void *)go_get_argument(ctx, is_register_abi, 5);
        secret_ptr = (void *)go_get_argument(ctx, is_register_abi, 7);
        secret_len_ptr = (void *)go_get_argument(ctx, is_register_abi, 8);
    
        bpf_probe_read_kernel(&lab_len, sizeof(lab_len), (void *)&lab_len_ptr);
        bpf_probe_read_kernel(&cr_len, sizeof(lab_len), (void *)&cr_len_ptr);
        bpf_probe_read_kernel(&secret_len, sizeof(lab_len),
                              (void *)&secret_len_ptr);
    
        if (lab_len <= 0 || cr_len <= 0 || secret_len <= 0) {
            return 0;
        }
    
        debug_bpf_printk(
            "gotls_mastersecret read params length success, lab_len:%d, cr_len:%d, "
            "secret_len:%d\n",
            lab_len, cr_len, secret_len);
    
        struct mastersecret_gotls_t mastersecret_gotls = {};
        mastersecret_gotls.labellen = lab_len;
        mastersecret_gotls.client_random_len = cr_len;
        mastersecret_gotls.secret_len = secret_len;
        int ret = bpf_probe_read_user_str(&mastersecret_gotls.label,
                                          sizeof(mastersecret_gotls.label),
                                          (void *)lab_ptr);
        if (ret < 0) {
            debug_bpf_printk(
                "gotls_mastersecret read mastersecret label failed, ret:%d, "
                "lab_ptr:%p\n",
                ret, lab_ptr);
            return 0;
        }
    
        debug_bpf_printk("gotls_mastersecret read mastersecret label:%s\n",
                         mastersecret_gotls.label);
        ret = bpf_probe_read_user_str(&mastersecret_gotls.client_random,
                                      sizeof(mastersecret_gotls.client_random),
                                      (void *)cr_ptr);
        if (ret < 0) {
            debug_bpf_printk(
                "gotls_mastersecret read mastersecret client_random failed, "
                "ret:%d, cr_ptr:%p\n",
                ret, cr_ptr);
            return 0;
        }
    
        ret = bpf_probe_read_user_str(&mastersecret_gotls.secret_,
                                      sizeof(mastersecret_gotls.secret_),
                                      (void *)secret_ptr);
        if (ret < 0) {
            debug_bpf_printk(
                "gotls_mastersecret read mastersecret secret_ failed, ret:%d, "
                "secret_ptr:%p\n",
                ret, secret_ptr);
            return 0;
        }
    
        bpf_perf_event_output(ctx, &mastersecret_go_events, BPF_F_CURRENT_CPU,
                              &mastersecret_gotls,
                              sizeof(struct mastersecret_gotls_t));
        return 0;
    }