前言
云原生生态中,golang语言开发的项目越来越多,例如Docker和K8s、etcd等。作为SRE、RD,偶尔需要在生产环境抓网络通讯包,用来分析排查故障。很多时候,都是tls/https加密协议,如何在不重启业务保留现场,不改为自定义CA证书的情况下,分析明文通讯内容呢?
适用场景
eCapture 0.5.0版本在2023年3月12日发布,支持了go语言编写的软件的tls/https明文抓包。只需要root权限,即可捕获并保存为pcapng格式,使用wireshark即可打开查看。
使用方法
eecapture gotls -h
bin/ecapture gotls -h
NAME:
gotls - capture golang tls/https text content without CA cert for ELF compile by Golang toolchain
USAGE:
ecapture gotls [flags]
DESCRIPTION:
use eBPF uprobe/TC to capture process event data and network data. also support pcap-NG format.
ecapture gotls
ecapture gotls --elfpath=/home/cfc4n/go_https_client --hex --pid=3423
ecapture gotls --elfpath=/home/cfc4n/go_https_client -l save.log --pid=3423
ecapture gotls -w save_android.pcapng -i wlan0 --port 443 --elfpath=/home/cfc4n/go_https_client
OPTIONS:
-e, --elfpath="" ELF path to binary built with Go toolchain.
-h, --help[=false] help for gotls
-i, --ifname="" (TC Classifier) Interface name on which the probe will be attached.
--port=443 port number to capture, default:443.
-w, --write="" write the raw packets to file as pcapng format.
GLOBAL OPTIONS:
-d, --debug[=false] enable debug logging
--hex[=false] print byte strings as hex encoded strings
-l, --log-file="" -l save the packets to file
--nosearch[=false] no lib search
-p, --pid=0 if pid is 0 then we target all pids
-u, --uid=0 if uid is 0 then we target all users
举个例子
/path/elf_filepath_compiled_by_go
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: %snn", 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
}
tcpdump
./ecapture gotls -e=/path/elf_filepath_compiled_by_go -w a.pcapng -i eth0
Wireshark打开网络包
下载地址
以下内容,为功能实现原理,若你只是使用,可跳过。
技术原理
Probe参数获取
这里有一张Golang的函数参数、返回值的寄存器传递布局,供参考。更多内容可以在线阅读《Go语言高级编程》中文版
eCapture的参数获取实现,可以阅读kern/go_argument.h
Probe参数选择
crypto/tls/common.gowriteKeyLog
Golang函数参数传递
writeKeyLogstringslice
// runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
// runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
stringstr unsafe.Pointerlen int
eCapture的实现:
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);
Golang uretprobe
crypto/tls.(*Conn).writeRecordLockedcrypto/tls.(*Conn).Readuretprobeuretprobe
iovisor/bcc[Brendan Gregg](https://github.com/brendangregg)
uprobeReaduprobe