From 07180ab9d6c5577b66526201f202d379328cdcd3 Mon Sep 17 00:00:00 2001 From: Hengqi Chen Date: Tue, 21 Jun 2022 19:55:08 +0800 Subject: [PATCH] Support Go binaries introspection Introduce minimal support for Golang HTTPS request introspection. Signed-off-by: Hengqi Chen --- Makefile | 3 +- cli/cmd/tls.go | 6 ++- kern/gossl_kern.c | 104 ++++++++++++++++++++++++++++++++++++++ user/config_gossl.go | 26 ++++++++++ user/const.go | 1 + user/event_gossl.go | 52 +++++++++++++++++++ user/probe_gossl.go | 117 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 kern/gossl_kern.c create mode 100644 user/config_gossl.go create mode 100644 user/event_gossl.go create mode 100644 user/probe_gossl.go diff --git a/Makefile b/Makefile index 91d350471e..eea07e5a2f 100644 --- a/Makefile +++ b/Makefile @@ -208,6 +208,7 @@ TARGETS += kern/gnutls TARGETS += kern/nspr TARGETS += kern/mysqld TARGETS += kern/postgres +TARGETS += kern/gossl # Generate file name-scheme based on TARGETS KERN_SOURCES = ${TARGETS:=_kern.c} @@ -351,4 +352,4 @@ format: @clang-format -i -style=$(STYLE) kern/common.h autogen: .checkver_$(CMD_BPFTOOL) - $(AUTOGENCMD) \ No newline at end of file + $(AUTOGENCMD) diff --git a/cli/cmd/tls.go b/cli/cmd/tls.go index cd3eede569..7aeca2e920 100644 --- a/cli/cmd/tls.go +++ b/cli/cmd/tls.go @@ -18,6 +18,7 @@ import ( var oc = user.NewOpensslConfig() var gc = user.NewGnutlsConfig() var nc = user.NewNsprConfig() +var goc = user.NewGoSSLConfig() // opensslCmd represents the openssl command var opensslCmd = &cobra.Command{ @@ -38,6 +39,7 @@ func init() { opensslCmd.PersistentFlags().StringVar(&nc.Firefoxpath, "firefox", "", "firefox file path, default: /usr/lib/firefox/firefox.") opensslCmd.PersistentFlags().StringVar(&nc.Nsprpath, "nspr", "", "libnspr44.so file path, will automatically find it from curl default.") opensslCmd.PersistentFlags().StringVar(&oc.Pthread, "pthread", "", "libpthread.so file path, use to hook connect to capture socket FD.will automatically find it from curl.") + opensslCmd.PersistentFlags().StringVar(&goc.Path, "gobin", "", "path to binary built with Go toolchain.") rootCmd.AddCommand(opensslCmd) } @@ -57,7 +59,7 @@ func openSSLCommandFunc(command *cobra.Command, args []string) { } log.Printf("pid info :%d", os.Getpid()) - modNames := []string{user.MODULE_NAME_OPENSSL, user.MODULE_NAME_GNUTLS, user.MODULE_NAME_NSPR} + modNames := []string{user.MODULE_NAME_OPENSSL, user.MODULE_NAME_GNUTLS, user.MODULE_NAME_NSPR, user.MODULE_NAME_GOSSL} var runMods uint8 for _, modName := range modNames { @@ -76,6 +78,8 @@ func openSSLCommandFunc(command *cobra.Command, args []string) { conf = gc case user.MODULE_NAME_NSPR: conf = nc + case user.MODULE_NAME_GOSSL: + conf = goc default: } diff --git a/kern/gossl_kern.c b/kern/gossl_kern.c new file mode 100644 index 0000000000..235005b07a --- /dev/null +++ b/kern/gossl_kern.c @@ -0,0 +1,104 @@ +/* Copyright © 2022 Hengqi Chen */ +#include "ecapture.h" + +struct go_ssl_event { + __u64 ts_ns; + __u32 pid; + __u32 tid; + int data_len; + char comm[TASK_COMM_LEN]; + char data[MAX_DATA_SIZE_OPENSSL]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); +} events SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, struct go_ssl_event); + __uint(max_entries, 1); +} heap SEC(".maps"); + +#if defined(__TARGET_ARCH_x86) + #define GO_REG1(x) ((x)->ax) + #define GO_REG2(x) ((x)->bx) + #define GO_REG3(x) ((x)->cx) + #define GO_REG4(x) ((x)->di) + #define GO_SP(x) ((x)->sp) +#elif defined(__TARGET_ARCH_arm64) + #define GO_REG1(x) PT_REGS_PARM1(x) + #define GO_REG2(x) PT_REGS_PARM2(x) + #define GO_REG3(x) PT_REGS_PARM3(x) + #define GO_REG4(x) PT_REGS_PARM4(x) + #define GO_SP(x) PT_REGS_SP(x) +#endif + +static struct go_ssl_event *get_event() +{ + static const int zero = 0; + struct go_ssl_event *event; + __u64 id; + + event = bpf_map_lookup_elem(&heap, &zero); + if (!event) + return NULL; + + id = bpf_get_current_pid_tgid(); + event->ts_ns = bpf_ktime_get_ns(); + event->pid = id >> 32; + event->tid = (__u32)id; + bpf_get_current_comm(event->comm, sizeof(event->comm)); + return event; +} + +SEC("uprobe/abi_stack") +int BPF_KPROBE(probe_stack) +{ + struct go_ssl_event *event; + __u64 *sp = (void *)GO_SP(ctx), addr; + int len, record_type; + const char *str; + + bpf_probe_read_user(&record_type, sizeof(record_type), sp + 2); + if (record_type != 23) + return 0; + + bpf_probe_read_user(&addr, sizeof(addr), sp + 3); + bpf_probe_read_user(&len, sizeof(len), sp + 4); + + event = get_event(); + if (!event) + return 0; + + str = (void *)addr; + bpf_probe_read_user_str(event->data, sizeof(event->data), str); + event->data_len = len; + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event)); + return 0; +} + +SEC("uprobe/abi_register") +int BPF_KPROBE(probe_register) +{ + struct go_ssl_event *event; + int len, record_type; + const char *str; + + record_type = GO_REG2(ctx); + str = (void *)GO_REG3(ctx); + len = GO_REG4(ctx); + + if (record_type != 23) + return 0; + + event = get_event(); + if (!event) + return 0; + + bpf_probe_read_user_str(event->data, sizeof(event->data), str); + event->data_len = len; + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event)); + return 0; +} diff --git a/user/config_gossl.go b/user/config_gossl.go new file mode 100644 index 0000000000..8290654873 --- /dev/null +++ b/user/config_gossl.go @@ -0,0 +1,26 @@ +// Copyright © 2022 Hengqi Chen +package user + +import ( + "errors" + "os" +) + +// GoSSLConfig represents configuration for Go SSL probe +type GoSSLConfig struct { + eConfig + Path string +} + +// NewGoSSLConfig creates a new config for Go SSL +func NewGoSSLConfig() *GoSSLConfig { + return &GoSSLConfig{} +} + +func (c *GoSSLConfig) Check() error { + if c.Path == "" { + return errors.New("go binary not found") + } + _, err := os.Stat(c.Path) + return err +} diff --git a/user/const.go b/user/const.go index 5601528e5f..775593c236 100644 --- a/user/const.go +++ b/user/const.go @@ -14,6 +14,7 @@ const ( MODULE_NAME_OPENSSL = "EBPFProbeOPENSSL" MODULE_NAME_GNUTLS = "EBPFProbeGNUTLS" MODULE_NAME_NSPR = "EBPFProbeNSPR" + MODULE_NAME_GOSSL = "EBPFProbeGoSSL" ) const ( diff --git a/user/event_gossl.go b/user/event_gossl.go new file mode 100644 index 0000000000..fa1fcb277f --- /dev/null +++ b/user/event_gossl.go @@ -0,0 +1,52 @@ +// Copyright © 2022 Hengqi Chen +package user + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type inner struct { + TimestampNS uint64 + Pid uint32 + Tid uint32 + Len int32 + Comm [16]byte + Data [4096]byte +} + +type goSSLEvent struct { + m IModule + inner +} + +func (e *goSSLEvent) Decode(payload []byte) error { + r := bytes.NewBuffer(payload) + return binary.Read(r, binary.LittleEndian, &e.inner) +} + +func (e *goSSLEvent) String() string { + s := fmt.Sprintf("PID: %d, Comm: %s, TID: %d, Payload: %s\n", e.Pid, string(e.Comm[:]), e.Tid, string(e.Data[:e.Len])) + return s +} + +func (e *goSSLEvent) StringHex() string { + return e.String() +} + +func (e *goSSLEvent) Clone() IEventStruct { + return &goSSLEvent{} +} + +func (e *goSSLEvent) Module() IModule { + return e.m +} + +func (e *goSSLEvent) SetModule(m IModule) { + e.m = m +} + +func (e *goSSLEvent) EventType() EVENT_TYPE { + return EVENT_TYPE_OUTPUT +} diff --git a/user/probe_gossl.go b/user/probe_gossl.go new file mode 100644 index 0000000000..3c36314255 --- /dev/null +++ b/user/probe_gossl.go @@ -0,0 +1,117 @@ +// Copyright © 2022 Hengqi Chen +package user + +import ( + "bytes" + "context" + "ecapture/assets" + "ecapture/pkg/proc" + "log" + "math" + + "github.com/cilium/ebpf" + manager "github.com/ehids/ebpfmanager" + "golang.org/x/sys/unix" +) + +func init() { + mod := &GoSSLProbe{} + Register(mod) +} + +// GoSSLProbe represents a probe for Go SSL +type GoSSLProbe struct { + Module + + mngr *manager.Manager + path string + isRegisterABI bool +} + +func (p *GoSSLProbe) Init(ctx context.Context, l *log.Logger, cfg IConfig) error { + p.Module.Init(ctx, l) + p.Module.SetChild(p) + + p.path = cfg.(*GoSSLConfig).Path + ver, err := proc.ExtraceGoVersion(p.path) + if err != nil { + return err + } + + if ver.After(1, 15) { + p.isRegisterABI = true + } + return nil +} + +func (p *GoSSLProbe) Name() string { + return MODULE_NAME_GOSSL +} + +func (p *GoSSLProbe) Start() error { + var ( + sec string + fn string + ) + + if p.isRegisterABI { + sec = "uprobe/abi_register" + fn = "probe_register" + } else { + sec = "uprobe/abi_stack" + fn = "probe_stack" + } + + p.mngr = &manager.Manager{ + Probes: []*manager.Probe{ + { + Section: sec, + EbpfFuncName: fn, + AttachToFuncName: "crypto/tls.(*Conn).writeRecordLocked", + BinaryPath: p.path, + }, + }, + Maps: []*manager.Map{ + { + Name: "events", + }, + }, + } + + data, err := assets.Asset("user/bytecode/gossl_kern.o") + if err != nil { + return err + } + + opts := manager.Options{ + RLimit: &unix.Rlimit{ + Cur: math.MaxUint64, + Max: math.MaxUint64, + }, + } + if err := p.mngr.InitWithOptions(bytes.NewReader(data), opts); err != nil { + return err + } + + return p.mngr.Start() +} + +func (p *GoSSLProbe) Events() []*ebpf.Map { + var maps []*ebpf.Map + + m, ok, err := p.mngr.GetMap("events") + if err != nil || !ok { + return maps + } + + maps = append(maps, m) + return maps +} + +func (p *GoSSLProbe) DecodeFun(m *ebpf.Map) (IEventStruct, bool) { + return &goSSLEvent{}, true +} + +func (p *GoSSLProbe) Close() error { + return nil +}