Skip to content

Commit

Permalink
Merge pull request #100 from chenhengqi/golang-https
Browse files Browse the repository at this point in the history
Support Golang HTTPS introspection

Only support x86_64 `Conn.Write()` .

TODO : support arm64  and  `Conn.Read()`
  • Loading branch information
cfc4n authored Jun 22, 2022
2 parents ebb1464 + a95854a commit 2232413
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 3 deletions.
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

0 comments on commit 2232413

Please sign in to comment.