mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-10-28 15:31:46 +08:00
136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
//go:build tracer
|
|
|
|
// Package tracer uses Linux Kprobes to gather ephemeral keys from handshakes of local WireGuard interfaces
|
|
//
|
|
// Tested with Linux 5.15.0
|
|
package tracer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cilium/ebpf/link"
|
|
"github.com/cilium/ebpf/ringbuf"
|
|
"github.com/cilium/ebpf/rlimit"
|
|
"github.com/stv0g/cunicu/pkg/wg/tracer/kernel"
|
|
)
|
|
|
|
//go:generate make -C kernel config.h
|
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags tracer -cc clang -type handshake -target $GOARCH bpf kprobe_wg_index_hashtable_insert.c -- -I include
|
|
|
|
var bootTime time.Time
|
|
|
|
type HandshakeTracer struct {
|
|
Handshakes chan *Handshake
|
|
Errors chan error
|
|
|
|
reader *ringbuf.Reader
|
|
kprobe link.Link
|
|
}
|
|
|
|
func NewHandshakeTracer() (*HandshakeTracer, error) {
|
|
var err error
|
|
|
|
// Allow the current process to lock memory for eBPF resources.
|
|
if err := rlimit.RemoveMemlock(); err != nil {
|
|
return nil, fmt.Errorf("failed to remove memlock: %w", err)
|
|
}
|
|
|
|
ht := &HandshakeTracer{
|
|
Errors: make(chan error),
|
|
Handshakes: make(chan *Handshake),
|
|
}
|
|
|
|
if err := ht.Check(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load pre-compiled programs and maps into the kernel.
|
|
objs := bpfObjects{}
|
|
if err := loadBpfObjects(&objs, nil); err != nil {
|
|
return nil, fmt.Errorf("failed loading objects: %w", err)
|
|
}
|
|
defer objs.Close()
|
|
|
|
// Open a Kprobe at the entry point of the kernel function and attach the
|
|
// pre-compiled program. Each time the kernel function enters, the program
|
|
// will emit an event containing pid and command of the execved task.
|
|
if ht.kprobe, err = link.Kprobe("wg_index_hashtable_insert", objs.KprobeWgIndexHashtableInsert, nil); err != nil {
|
|
return nil, fmt.Errorf("failed opening kprobe: %w", err)
|
|
}
|
|
|
|
// Open a ringbuf reader from userspace RINGBUF map described in the
|
|
// eBPF C program.
|
|
ht.reader, err = ringbuf.NewReader(objs.Handshakes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed opening ringbuf reader: %w", err)
|
|
}
|
|
|
|
go ht.run()
|
|
|
|
return ht, nil
|
|
}
|
|
|
|
func charsToString(is []int8) string {
|
|
bs := []byte{}
|
|
for i := 0; is[i] != 0; i++ {
|
|
bs = append(bs, byte(is[i]))
|
|
}
|
|
return string(bs)
|
|
}
|
|
|
|
func (ht *HandshakeTracer) Check() error {
|
|
uts := &syscall.Utsname{}
|
|
if err := syscall.Uname(uts); err != nil {
|
|
return fmt.Errorf("failed to get utsname: %w", err)
|
|
}
|
|
|
|
machine := charsToString(uts.Machine[:])
|
|
release := charsToString(uts.Release[:])
|
|
|
|
if machine != kernel.TargetMachine {
|
|
return fmt.Errorf("machine mismatch: %s != %s", machine, kernel.TargetMachine)
|
|
}
|
|
|
|
if !strings.HasPrefix(release, kernel.TargetRelease) {
|
|
return fmt.Errorf("release mismatch: %s != %s", release, kernel.TargetRelease)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ht *HandshakeTracer) Close() error {
|
|
if err := ht.reader.Close(); err != nil {
|
|
return fmt.Errorf("failed closing ringbuf reader: %w", err)
|
|
}
|
|
|
|
if err := ht.kprobe.Close(); err != nil {
|
|
return fmt.Errorf("failed to close kprobe: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ht *HandshakeTracer) run() {
|
|
for {
|
|
record, err := ht.reader.Read()
|
|
if err != nil {
|
|
if errors.Is(err, ringbuf.ErrClosed) {
|
|
return
|
|
}
|
|
|
|
ht.Errors <- fmt.Errorf("failed to read from ringbuf: %w", err)
|
|
continue
|
|
}
|
|
|
|
if hs, err := HandshakeFromBPFRecord(record); err != nil {
|
|
ht.Errors <- fmt.Errorf("failed to parse handshake from record: %w", err)
|
|
} else {
|
|
ht.Handshakes <- hs
|
|
}
|
|
}
|
|
}
|