Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Golang HTTPS introspection #100

Merged
merged 3 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@

# VSCode
.vscode/settings.json

kern/bpf/x86/vmlinux.h
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -351,4 +352,4 @@ format:
@clang-format -i -style=$(STYLE) kern/common.h

autogen: .checkver_$(CMD_BPFTOOL)
$(AUTOGENCMD)
$(AUTOGENCMD)
6 changes: 5 additions & 1 deletion cli/cmd/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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:
}

Expand Down
3 changes: 2 additions & 1 deletion kern/ecapture.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef NOCORE
//CO:RE is enabled
#include "vmlinux.h"
#include "bpf/bpf_core_read.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_tracing.h"

Expand All @@ -18,4 +19,4 @@

#include "common.h"

#endif
#endif
124 changes: 124 additions & 0 deletions kern/gossl_kern.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* 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");

#ifndef NOCORE

#if defined(__TARGET_ARCH_x86)
#define GO_REG1(x) BPF_CORE_READ((x), ax)
#define GO_REG2(x) BPF_CORE_READ((x), bx)
#define GO_REG3(x) BPF_CORE_READ((x), cx)
#define GO_REG4(x) BPF_CORE_READ((x), di)
#define GO_SP(x) BPF_CORE_READ((x), sp)
#elif defined(__TARGET_ARCH_arm64)
#define GO_REG1(x) PT_REGS_PARM1_CORE(x)
#define GO_REG2(x) PT_REGS_PARM2_CORE(x)
#define GO_REG3(x) PT_REGS_PARM3_CORE(x)
#define GO_REG4(x) PT_REGS_PARM4_CORE(x)
#define GO_SP(x) PT_REGS_SP_CORE(x)
#endif

#else

#if defined(__x86_64__)
#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(__aarch64__)
#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

#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;
}
99 changes: 99 additions & 0 deletions pkg/proc/proc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package proc

import (
"debug/dwarf"
"debug/elf"
"errors"
"strconv"
"strings"
)

const (
goVersionPrefix = "Go cmd/compile "
)

// ErrVersionNotFound is returned when we can't find Go version info from a binary
var ErrVersionNotFound = errors.New("version info not found")

// GoVersion represents Go toolchain version that a binary is built with.
type GoVersion struct {
major int
minor int
}

// After returns true if it is greater than major.minor
func (v *GoVersion) After(major, minor int) bool {
if v.major > minor {
return true
}
if v.major == major && v.minor > minor {
return true
}
return false
}

// ExtraceGoVersion extracts Go version info from a binary that is built with Go toolchain
func ExtraceGoVersion(path string) (*GoVersion, error) {
file, err := elf.Open(path)
if err != nil {
return nil, err
}
defer file.Close()

raw, err := file.DWARF()
if err != nil {
return nil, err
}

reader := raw.Reader()
for {
entry, err := reader.Next()
if err != nil {
return nil, err
}

if entry == nil {
break
}

for _, field := range entry.Field {
if field.Attr == dwarf.AttrProducer {
val, ok := field.Val.(string)
if !ok {
continue
}
return parseGoVersion(val)
}
}
}

return nil, ErrVersionNotFound
}

func parseGoVersion(r string) (*GoVersion, error) {
ver := strings.TrimPrefix(r, goVersionPrefix)

if strings.HasPrefix(ver, "go") {
v := strings.SplitN(ver[2:], ".", 3)
var major, minor int
var err error

major, err = strconv.Atoi(v[0])
if err != nil {
return nil, err
}

if len(v) >= 2 {
minor, err = strconv.Atoi(v[1])
if err != nil {
return nil, err
}
}

return &GoVersion{
major: major,
minor: minor,
}, nil
}
return nil, ErrVersionNotFound
}
18 changes: 18 additions & 0 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package proc

import (
"fmt"
"log"
"os"
"testing"
)

func TestExtraceGoVersion(t *testing.T) {
path := fmt.Sprintf("/proc/%d/exe", os.Getppid())
ver, err := ExtraceGoVersion(path)
if err != nil {
t.Log(err)
return
}
log.Println(ver)
}
26 changes: 26 additions & 0 deletions user/config_gossl.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions user/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
MODULE_NAME_OPENSSL = "EBPFProbeOPENSSL"
MODULE_NAME_GNUTLS = "EBPFProbeGNUTLS"
MODULE_NAME_NSPR = "EBPFProbeNSPR"
MODULE_NAME_GOSSL = "EBPFProbeGoSSL"
)

const (
Expand Down
Loading