-
Notifications
You must be signed in to change notification settings - Fork 8
Hooks into stripped binaries for SSL calls #163
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
base: master
Are you sure you want to change the base?
Changes from 7 commits
fa75c1b
114faef
8708c1c
a7ae416
ad28756
4339a05
c8b78aa
6e84367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,40 @@ | ||
| package ssl | ||
|
|
||
| import ( | ||
| "debug/elf" | ||
| "fmt" | ||
| "path/filepath" | ||
| "strconv" | ||
|
|
||
| "github.com/cilium/ebpf/link" | ||
| "github.com/go-errors/errors" | ||
| lru "github.com/hashicorp/golang-lru/v2" | ||
| "github.com/kubeshark/offsetdb/hasher" | ||
| "github.com/kubeshark/offsetdb/models" | ||
| "github.com/kubeshark/offsetdb/store" | ||
| "github.com/kubeshark/tracer/pkg/bpf" | ||
| "github.com/kubeshark/tracer/pkg/utils" | ||
| ) | ||
|
|
||
| const ( | ||
| offsetdb = "/app/offsets.json" | ||
| ) | ||
|
|
||
| var offStore = store.NewOffsetStore() | ||
|
|
||
| type SslHooks struct { | ||
| links []link.Link | ||
| } | ||
|
|
||
| // TODO: incapsulate, add devuce id to the key, delete on file is deleted | ||
| var hookInodes, _ = lru.New[uint64, uint32](16384) | ||
|
|
||
| func init() { | ||
| if err := offStore.LoadOffsets(offsetdb); err != nil { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This must be duplication of onceFunc part - to be removed |
||
| panic(err) | ||
|
||
| } | ||
| } | ||
|
|
||
| func (s *SslHooks) InstallUprobes(bpfObjects *bpf.BpfObjects, sslLibraryPath string) error { | ||
| var isEnvoy bool | ||
| if filepath.Base(sslLibraryPath) == "envoy" { | ||
|
|
@@ -32,13 +50,25 @@ func (s *SslHooks) InstallUprobes(bpfObjects *bpf.BpfObjects, sslLibraryPath str | |
| } | ||
|
|
||
| sslLibrary, err := link.OpenExecutable(sslLibraryPath) | ||
|
|
||
| if err != nil { | ||
| return errors.Wrap(err, 0) | ||
| } | ||
|
|
||
| if isEnvoy { | ||
| return s.installEnvoySslHooks(bpfObjects, sslLibrary) | ||
| // Compute the has of the binary | ||
iluxa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| hash, err := hasher.ComputeFileSHA256(sslLibraryPath) | ||
| if err != nil { | ||
| return fmt.Errorf("fallback: sha256 failed: %w", err) | ||
| } | ||
|
|
||
| // Check if the hash is in the offset store | ||
| info, found := offStore.GetOffsets(hash) | ||
| if !found { | ||
| // Try to install the hooks by symbols | ||
| return s.installEnvoySslHooks(bpfObjects, sslLibrary) | ||
| } | ||
|
|
||
| return s.installEnvoySslHooksWithOffset(bpfObjects, sslLibrary, sslLibraryPath, info) | ||
| } | ||
|
|
||
| return s.installSslHooks(bpfObjects, sslLibrary) | ||
|
|
@@ -145,3 +175,109 @@ func (s *SslHooks) Close() []error { | |
|
|
||
| return returnValue | ||
| } | ||
|
|
||
| func (s *SslHooks) installEnvoySslHooksWithOffset( | ||
| bpfObjects *bpf.BpfObjects, | ||
| sslLibrary *link.Executable, | ||
| sslLibraryPath string, | ||
| info *models.OffsetInfo, | ||
| ) error { | ||
| var err error | ||
| var relativeOffset, baseOffset, absoluteOffset uint64 | ||
|
|
||
| baseOffset, err = findStrippedExecutableSegmentOffset(sslLibraryPath) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to find base offset in SSL library '%s': %w", sslLibraryPath, err) | ||
| } | ||
|
|
||
| // --- SSL_write --- | ||
| if relativeOffset, err = parseOffset(info.SSLWriteOffset); err != nil { | ||
| return fmt.Errorf("parsing SSLWriteOffset: %w", err) | ||
| } | ||
| absoluteOffset = baseOffset + relativeOffset | ||
|
|
||
| // ENTRY SSL_write | ||
| upWrite, err := sslLibrary.Uprobe( | ||
| "", | ||
| bpfObjects.BpfObjs.SslWrite, | ||
| &link.UprobeOptions{Address: absoluteOffset}, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("attaching SSL_write uprobe at offset 0x%x : %w", absoluteOffset, err) | ||
| } | ||
| s.links = append(s.links, upWrite) | ||
|
|
||
| // EXIT SSL_write (uses the same address as the entry) | ||
| urWrite, err := sslLibrary.Uretprobe( | ||
| "", | ||
| bpfObjects.BpfObjs.SslRetWrite, | ||
| &link.UprobeOptions{Address: absoluteOffset}, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("attaching SSL_write uretprobe at offset 0x%x : %w", absoluteOffset, err) | ||
| } | ||
| s.links = append(s.links, urWrite) | ||
|
|
||
| // --- SSL_read --- | ||
| if relativeOffset, err = parseOffset(info.SSLReadOffset); err != nil { | ||
| return fmt.Errorf("parsing SSLReadOffset: %w", err) | ||
| } | ||
| absoluteOffset = baseOffset + relativeOffset | ||
|
|
||
| // ENTRY SSL_read | ||
| upRead, err := sslLibrary.Uprobe( | ||
| "", | ||
| bpfObjects.BpfObjs.SslRead, | ||
| &link.UprobeOptions{Address: absoluteOffset}, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("attaching SSL_read uprobe at offset 0x%x : %w", absoluteOffset, err) | ||
| } | ||
| s.links = append(s.links, upRead) | ||
|
|
||
| // EXIT SSL_read (uses the same address as the entry) | ||
| urRead, err := sslLibrary.Uretprobe( | ||
| "", | ||
| bpfObjects.BpfObjs.SslRetRead, | ||
| &link.UprobeOptions{Address: absoluteOffset}, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("attaching SSL_read uretprobe at offset 0x%x : %w", absoluteOffset, err) | ||
| } | ||
| s.links = append(s.links, urRead) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // parseOffset turns a hex- or dec-formatted string into a uint64 | ||
| func parseOffset(s string) (uint64, error) { | ||
| // Let strconv auto-detect the base from “0x…” prefix or plain digits | ||
| val, err := strconv.ParseUint(s, 0, 64) | ||
| if err != nil { | ||
| return 0, fmt.Errorf("invalid offset %q: %w", s, err) | ||
| } | ||
| return val, nil | ||
| } | ||
|
|
||
| // findStrippedExecutableSegmentOffset finds the file offset of the first executable segment | ||
| // or the .text section in an ELF file. | ||
| func findStrippedExecutableSegmentOffset(path string) (uint64, error) { | ||
| f, err := elf.Open(path) | ||
| if err != nil { | ||
| return 0, fmt.Errorf("elf.Open %s: %w", path, err) | ||
| } | ||
| defer f.Close() | ||
|
|
||
| // Prefer .text section offset when available | ||
| if sec := f.Section(".text"); sec != nil && sec.Offset != 0 { | ||
| return sec.Offset, nil | ||
| } | ||
|
|
||
| // Otherwise, pick the first executable PT_LOAD | ||
| for _, prog := range f.Progs { | ||
| if prog.Type == elf.PT_LOAD && (prog.Flags&elf.PF_X) != 0 { | ||
| return prog.Off, nil | ||
| } | ||
| } | ||
| return 0, errors.New("no executable segment or .text section found") | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.