mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-08-19 08:59:16 +00:00
* Fix a `slice bounds out of range` error in Go offsets calculation * Change `Errorf` to `Warningf`
225 lines
5.1 KiB
Go
225 lines
5.1 KiB
Go
package tlstapper
|
|
|
|
import (
|
|
"bufio"
|
|
"debug/elf"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
|
|
"github.com/Masterminds/semver"
|
|
"github.com/cilium/ebpf/link"
|
|
"github.com/knightsc/gapstone"
|
|
"github.com/up9inc/mizu/logger"
|
|
)
|
|
|
|
type goOffsets struct {
|
|
GoWriteOffset *goExtendedOffset
|
|
GoReadOffset *goExtendedOffset
|
|
}
|
|
|
|
type goExtendedOffset struct {
|
|
enter uint64
|
|
exits []uint64
|
|
}
|
|
|
|
const (
|
|
minimumSupportedGoVersion = "1.17.0"
|
|
goVersionSymbol = "runtime.buildVersion.str"
|
|
goWriteSymbol = "crypto/tls.(*Conn).Write"
|
|
goReadSymbol = "crypto/tls.(*Conn).Read"
|
|
)
|
|
|
|
func findGoOffsets(filePath string) (goOffsets, error) {
|
|
offsets, err := getOffsets(filePath)
|
|
if err != nil {
|
|
return goOffsets{}, err
|
|
}
|
|
|
|
goVersionOffset, err := getOffset(offsets, goVersionSymbol)
|
|
if err != nil {
|
|
return goOffsets{}, err
|
|
}
|
|
|
|
passed, goVersion, err := checkGoVersion(filePath, goVersionOffset)
|
|
if err != nil {
|
|
return goOffsets{}, fmt.Errorf("Checking Go version: %s", err)
|
|
}
|
|
|
|
if !passed {
|
|
return goOffsets{}, fmt.Errorf("Unsupported Go version: %s", goVersion)
|
|
}
|
|
|
|
writeOffset, err := getOffset(offsets, goWriteSymbol)
|
|
if err != nil {
|
|
return goOffsets{}, fmt.Errorf("reading offset [%s]: %s", goWriteSymbol, err)
|
|
}
|
|
|
|
readOffset, err := getOffset(offsets, goReadSymbol)
|
|
if err != nil {
|
|
return goOffsets{}, fmt.Errorf("reading offset [%s]: %s", goReadSymbol, err)
|
|
}
|
|
|
|
return goOffsets{
|
|
GoWriteOffset: writeOffset,
|
|
GoReadOffset: readOffset,
|
|
}, nil
|
|
}
|
|
|
|
func getOffsets(filePath string) (offsets map[string]*goExtendedOffset, err error) {
|
|
var engine gapstone.Engine
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
engine, err = gapstone.New(
|
|
gapstone.CS_ARCH_X86,
|
|
gapstone.CS_MODE_64,
|
|
)
|
|
case "arm64":
|
|
engine, err = gapstone.New(
|
|
gapstone.CS_ARCH_ARM64,
|
|
gapstone.CS_MODE_ARM,
|
|
)
|
|
default:
|
|
err = fmt.Errorf("Unsupported architecture: %v", runtime.GOARCH)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
offsets = make(map[string]*goExtendedOffset)
|
|
var fd *os.File
|
|
fd, err = os.Open(filePath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer fd.Close()
|
|
|
|
var se *elf.File
|
|
se, err = elf.NewFile(fd)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
textSection := se.Section(".text")
|
|
if textSection == nil {
|
|
err = fmt.Errorf("No text section")
|
|
return
|
|
}
|
|
|
|
// extract the raw bytes from the .text section
|
|
var textSectionData []byte
|
|
textSectionData, err = textSection.Data()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
syms, err := se.Symbols()
|
|
for _, sym := range syms {
|
|
offset := sym.Value
|
|
|
|
var lastProg *elf.Prog
|
|
for _, prog := range se.Progs {
|
|
if prog.Vaddr <= sym.Value && sym.Value < (prog.Vaddr+prog.Memsz) {
|
|
offset = sym.Value - prog.Vaddr + prog.Off
|
|
lastProg = prog
|
|
break
|
|
}
|
|
}
|
|
|
|
extendedOffset := &goExtendedOffset{enter: offset}
|
|
|
|
// source: https://gist.github.com/grantseltzer/3efa8ecc5de1fb566e8091533050d608
|
|
// skip over any symbols that aren't functinons/methods
|
|
if sym.Info != byte(2) && sym.Info != byte(18) {
|
|
offsets[sym.Name] = extendedOffset
|
|
continue
|
|
}
|
|
|
|
// skip over empty symbols
|
|
if sym.Size == 0 {
|
|
offsets[sym.Name] = extendedOffset
|
|
continue
|
|
}
|
|
|
|
// calculate starting and ending index of the symbol within the text section
|
|
symStartingIndex := sym.Value - textSection.Addr
|
|
symEndingIndex := symStartingIndex + sym.Size
|
|
|
|
// collect the bytes of the symbol
|
|
textSectionDataLen := uint64(len(textSectionData) - 1)
|
|
if symEndingIndex > textSectionDataLen {
|
|
logger.Log.Warningf(
|
|
"Skipping symbol %v, ending index %v is bigger than text section data length %v",
|
|
sym.Name,
|
|
symEndingIndex,
|
|
textSectionDataLen,
|
|
)
|
|
continue
|
|
}
|
|
symBytes := textSectionData[symStartingIndex:symEndingIndex]
|
|
|
|
// disasemble the symbol
|
|
var instructions []gapstone.Instruction
|
|
instructions, err = engine.Disasm(symBytes, sym.Value, 0)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// iterate over each instruction and if the mnemonic is `ret` then that's an exit offset
|
|
for _, ins := range instructions {
|
|
if ins.Mnemonic == "ret" {
|
|
extendedOffset.exits = append(extendedOffset.exits, uint64(ins.Address)-lastProg.Vaddr+lastProg.Off)
|
|
}
|
|
}
|
|
|
|
offsets[sym.Name] = extendedOffset
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func getOffset(offsets map[string]*goExtendedOffset, symbol string) (*goExtendedOffset, error) {
|
|
if offset, ok := offsets[symbol]; ok {
|
|
return offset, nil
|
|
}
|
|
return nil, fmt.Errorf("symbol %s: %w", symbol, link.ErrNoSymbol)
|
|
}
|
|
|
|
func checkGoVersion(filePath string, offset *goExtendedOffset) (bool, string, error) {
|
|
fd, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
defer fd.Close()
|
|
|
|
reader := bufio.NewReader(fd)
|
|
|
|
_, err = reader.Discard(int(offset.enter))
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
|
|
line, err := reader.ReadString(0)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
|
|
if len(line) < 3 {
|
|
return false, "", fmt.Errorf("ELF data segment read error (corrupted result)")
|
|
}
|
|
|
|
goVersionStr := line[2 : len(line)-1]
|
|
|
|
goVersion, err := semver.NewVersion(goVersionStr)
|
|
if err != nil {
|
|
return false, goVersionStr, err
|
|
}
|
|
|
|
goVersionConstraint, err := semver.NewConstraint(fmt.Sprintf(">= %s", minimumSupportedGoVersion))
|
|
if err != nil {
|
|
return false, goVersionStr, err
|
|
}
|
|
|
|
return goVersionConstraint.Check(goVersion), goVersionStr, nil
|
|
}
|