eCapture旁观者支持Golang tls/https加密明文捕获
source link: https://www.cnxct.com/ecapture-supported-golang-tls-plaintext-captured/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
eCapture旁观者支持Golang tls/https加密明文捕获
云原生生态中,golang语言开发的项目越来越多,例如Docker和K8s、etcd等。作为SRE、RD,偶尔需要在生产环境抓网络通讯包,用来分析排查故障。很多时候,都是tls/https加密协议,如何在不重启业务保留现场,不改为自定义CA证书的情况下,分析明文通讯内容呢?
eCapture 0.5.0版本在2023年3月12日发布,支持了go语言编写的软件的tls/https明文抓包。只需要root权限,即可捕获并保存为pcapng格式,使用wireshark即可打开查看。
gotls模块的e
参数用来设定golang编译的可执行文件路径,可以通过ecapture 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
是一个go写的web服务,并且开启了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
}
可以使用如下命令,捕获明文通讯。 不用重启这个服务进程,也不需要做其他任何配置,就跟使用tcpdump
一样。
./ecapture gotls -e=/path/elf_filepath_compiled_by_go -w a.pcapng -i eth0
Wireshark打开网络包
eCapture Github仓库:https://github.com/gojue/ecapture/releases/tag/v0.5.0
韩国GitHub镜像:https://ghproxy.com/https://github.com/gojue/ecapture/releases/tag/v0.5.0
以下内容,为功能实现原理,若你只是使用,可跳过。
Probe参数获取
Golang的ABI不同于C,自定义了ABI机制。并且在go 1.17之前,使用的是栈方式传递调用参数;1.17以以后使用了寄存器方式传递调用参数。你可以阅读Register-based Go calling convention 了解更多知识。
这里有一张Golang的函数参数、返回值的寄存器传递布局,供参考。更多内容可以在线阅读《Go语言高级编程》中文版
eCapture的参数获取实现,可以阅读kern/go_argument.h
Probe参数选择
笔者这里hook的是Golang源码目录下crypto/tls/common.go
文件中的writeKeyLog
函数。用来捕获tls的master secret的label类别、clientRandom、密钥值等。
Golang函数参数传递
有个需要注意的地方,比如writeKeyLog
函数的第一个参数是string
类型,第二、三个参数是slice
类型。在Golang里,也都是一个结构体,如下代码:
// runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
// runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
以string
类型为例,在Go 的参数传递时,不光传递字符串的str unsafe.Pointer
指针地址,也还会传递len 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
在eCapture的文本模式中,需要在加密之前、解密之后拿到明文,对应的两个函数分别是crypto/tls.(*Conn).writeRecordLocked
和 crypto/tls.(*Conn).Read
。加密之前的获取只需要使用eBPF uprobe HOOK即可实现。而解密之后,则需要uretprobe
,但Golang里,uretprobe
的实现机制,会破坏他的堆栈,导致Golang程序进程崩溃。
这个问题,在iovisor/bcc
社区也有讨论:BCC issue: Go crash with uretprobe #1320,包括火焰图、eBPF的领导者Brendan Gregg,对这个问题也没有太好的办法。Gianluca Borello给了间接的解决方案,相对来说还是比较繁琐的,也有一定的crash风险,有兴趣的同学可以去看看。
eCapture里,加密之前的uprobe
已经完成hook,实现https/tls的请求内容明文捕获。但解密后的内容,暂时无法实现。笔者也在尝试其他思路,比如找到Read
返回值的调用函数,在哪里使用uprobe
实现,但这点逻辑比较偏业务层,调用者比较多,不太方便过滤。如果你有更好的办法,也欢迎提出来。
Hooking Go from Rust – Hitchhiker’s Guide to the Go-laxy
BCC issue: Go crash with uretprobe #1320
Interface method calls with the Go register ABI
CFC4N的博客 由 CFC4N 创作,采用 知识共享 署名-非商业性使用-相同方式共享(3.0未本地化版本)许可协议进行许可。基于https://www.cnxct.com上的作品创作。转载请注明转自:eCapture旁观者支持Golang tls/https加密明文捕获
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK