Skip to content

Commit

Permalink
feat: add brute force search fallback for elf
Browse files Browse the repository at this point in the history
  • Loading branch information
Zxilly committed Feb 13, 2024
1 parent de41912 commit 4b7e5ff
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 71 deletions.
93 changes: 86 additions & 7 deletions elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ type elfFile struct {
osFile *os.File
}

func (e *elfFile) getSymbolValue(s string) (uint64, error) {
syms, err := e.file.Symbols()
if err != nil {
return 0, fmt.Errorf("error when getting the symbols: %w", err)
}
for _, sym := range syms {
if sym.Name == s {
return sym.Value, nil
}
}
return 0, fmt.Errorf("symbol %s not found", s)
}

func (e *elfFile) getParsedFile() any {
return e.file
}
Expand All @@ -53,25 +66,91 @@ func (e *elfFile) getFile() *os.File {
return e.osFile
}

func (e *elfFile) getPCLNTab() (*gosym.Table, error) {
func (e *elfFile) getPCLNTab(textStart uint64) (table *gosym.Table, err error) {
var data []byte
pclnSection := e.file.Section(".gopclntab")
if pclnSection == nil {
// No section found. Check if the PIE section exist instead.
// No section found. Check if the PIE section exists instead.
pclnSection = e.file.Section(".data.rel.ro.gopclntab")
}
if pclnSection == nil {
return nil, fmt.Errorf("no gopclntab section found")
if pclnSection != nil {
data, err = pclnSection.Data()
if err != nil {
return nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
}
goto ret
}

// try to get data from symbol
data = e.symbolData("runtime.pclntab", "runtime.epclntab")
if data != nil {
goto ret
}

pclndat, err := pclnSection.Data()
// try brute force searching for the pclntab
_, data, err = e.searchForPclnTab()
if err != nil {
return nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
return nil, fmt.Errorf("could not find the pclntab: %w", err)
}

pcln := gosym.NewLineTable(pclndat, e.file.Section(".text").Addr)
ret:
pcln := gosym.NewLineTable(data, textStart)
return gosym.NewTable(make([]byte, 0), pcln)
}

func (e *elfFile) searchForPclnTab() (uint64, []byte, error) {
// Only use linkmode=external and buildmode=pie will lead to this
// Since the external linker merged all .data.rel.ro.* sections into .data.rel.ro
// So we have to find .data.rel.ro.gopclntab manually
sec := e.file.Section(".data.rel.ro")
if sec == nil {
return 0, nil, ErrNoPCLNTab
}
data, err := sec.Data()
if err != nil {
return 0, nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
}

tab, err := searchSectionForTab(data, e.getFileInfo().ByteOrder)
if err != nil {
return 0, nil, fmt.Errorf("could not find the pclntab: %w", err)
}
addr := sec.Addr + sec.FileSize - uint64(len(tab))
return addr, tab, nil
}

func (e *elfFile) symbolData(start, end string) []byte {
elfSyms, err := e.file.Symbols()
if err != nil {
return nil
}
var addr, eaddr uint64
for _, s := range elfSyms {
if s.Name == start {
addr = s.Value
} else if s.Name == end {
eaddr = s.Value
}
if addr != 0 && eaddr != 0 {
break
}
}
if addr == 0 || eaddr < addr {
return nil
}
size := eaddr - addr
data := make([]byte, size)
for _, prog := range e.file.Progs {
if prog.Vaddr <= addr && addr+size-1 <= prog.Vaddr+prog.Filesz-1 {
if _, err := prog.ReadAt(data, int64(addr-prog.Vaddr)); err != nil {
return nil
}
return data
}
}
return nil
}

func (e *elfFile) Close() error {
err := e.file.Close()
if err != nil {
Expand Down
13 changes: 11 additions & 2 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ func (f *GoFile) Moduledata() (Moduledata, error) {
return f.moduledata, nil
}

// initPackages initializes the packages in the binary.
// Note: this init depends on the moduledata initialization internal since PCLNTab() is called.
// any further modifications to moduledata initialization should consider this
// or a cycle dependency will be created.
func (f *GoFile) initPackages() error {
f.initPackagesOnce.Do(func() {
tab, err := f.PCLNTab()
Expand Down Expand Up @@ -377,7 +381,11 @@ func (f *GoFile) Close() error {

// PCLNTab returns the PCLN table.
func (f *GoFile) PCLNTab() (*gosym.Table, error) {
return f.fh.getPCLNTab()
err := f.initModuleData()
if err != nil {
return nil, err
}
return f.fh.getPCLNTab(f.moduledata.TextAddr)
}

// GetTypes returns a map of all types found in the binary file.
Expand Down Expand Up @@ -435,7 +443,8 @@ func sortTypes(types map[uint64]*GoType) []*GoType {

type fileHandler interface {
io.Closer
getPCLNTab() (*gosym.Table, error)
getSymbolValue(string) (uint64, error)
getPCLNTab(uint64) (*gosym.Table, error)
getRData() ([]byte, error)
getCodeSection() (uint64, []byte, error)
getSectionDataFromOffset(uint64) (uint64, []byte, error)
Expand Down
6 changes: 5 additions & 1 deletion file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ type mockFileHandler struct {
mGetSectionDataFromOffset func(uint64) (uint64, []byte, error)
}

func (m *mockFileHandler) getSymbolValue(s string) (uint64, error) {
panic("implement me")
}

func (m *mockFileHandler) getFile() *os.File {
panic("not implemented")
}
Expand All @@ -186,7 +190,7 @@ func (m *mockFileHandler) Close() error {
panic("not implemented")
}

func (m *mockFileHandler) getPCLNTab() (*gosym.Table, error) {
func (m *mockFileHandler) getPCLNTab(uint64) (*gosym.Table, error) {
panic("not implemented")
}

Expand Down
13 changes: 11 additions & 2 deletions macho.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ type machoFile struct {
osFile *os.File
}

func (m *machoFile) getSymbolValue(s string) (uint64, error) {
for _, sym := range m.file.Symtab.Syms {
if sym.Name == s {
return sym.Value, nil
}
}
return 0, fmt.Errorf("symbol %s not found", s)
}

func (m *machoFile) getParsedFile() any {
return m.file
}
Expand All @@ -60,7 +69,7 @@ func (m *machoFile) Close() error {
return m.osFile.Close()
}

func (m *machoFile) getPCLNTab() (*gosym.Table, error) {
func (m *machoFile) getPCLNTab(textStart uint64) (*gosym.Table, error) {
section := m.file.Section("__gopclntab")
if section == nil {
return nil, ErrNoPCLNTab
Expand All @@ -69,7 +78,7 @@ func (m *machoFile) getPCLNTab() (*gosym.Table, error) {
if data == nil {
return nil, err
}
pcln := gosym.NewLineTable(data, m.file.Section("__text").Addr)
pcln := gosym.NewLineTable(data, textStart)
return gosym.NewTable(nil, pcln)
}

Expand Down
51 changes: 42 additions & 9 deletions moduledata.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,7 @@ func pickVersionedModuleData(info *FileInfo) (modulable, error) {
return buf, nil
}

func extractModuledata(fileInfo *FileInfo, f fileHandler) (moduledata, error) {
vmd, err := pickVersionedModuleData(fileInfo)
if err != nil {
return moduledata{}, err
}

func searchModuledata(vmd modulable, fileInfo *FileInfo, f fileHandler) (moduledata, error) {
vmdSize := binary.Size(vmd)

_, secData, err := f.getSectionData(f.moduledataSection())
Expand Down Expand Up @@ -310,16 +305,54 @@ search:
goto invalidMD
}

// Add the file handler.
md.fh = f

return md, nil

invalidMD:
secData = secData[off+1:]
goto search
}

func readModuledataFromSymbol(vmd modulable, fileInfo *FileInfo, f fileHandler) (moduledata, error) {
vmdSize := binary.Size(vmd)

addr, err := f.getSymbolValue("runtime.firstmoduledata")
if err != nil {
return moduledata{}, err
}

base, data, err := f.getSectionDataFromOffset(addr)
if err != nil {
return moduledata{}, err
}

if addr-base+uint64(vmdSize) > uint64(len(data)) {
return moduledata{}, errors.New("moduledata is too big")
}

r := bytes.NewReader(data[addr-base : addr-base+uint64(vmdSize)])
err = binary.Read(r, fileInfo.ByteOrder, vmd)
if err != nil {
return moduledata{}, fmt.Errorf("error when reading module data from file: %w", err)
}

// Believe the symbol is correct, so no validation is needed.
return vmd.toModuledata(), nil
}

func extractModuledata(fileInfo *FileInfo, f fileHandler) (moduledata, error) {
vmd, err := pickVersionedModuleData(fileInfo)
if err != nil {
return moduledata{}, err
}

md, err := readModuledataFromSymbol(vmd, fileInfo, f)
if err == nil {
return md, nil
}

return searchModuledata(vmd, fileInfo, f)
}

func readUIntTo64(r io.Reader, byteOrder binary.ByteOrder, is32bit bool) (addr uint64, err error) {
if is32bit {
var addr32 uint32
Expand Down
63 changes: 17 additions & 46 deletions pclntab.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,27 @@ package gore

import (
"bytes"
"debug/pe"
"encoding/binary"
)

// pclntab12magic is the magic bytes used for binaries compiled with Go
// prior to 1.16
var pclntab12magic = []byte{0xfb, 0xff, 0xff, 0xff, 0x0, 0x0}

// pclntab116magic is the magic bytes used for binaries compiled with
// Go 1.16 and Go 1.17.
var pclntab116magic = []byte{0xfa, 0xff, 0xff, 0xff, 0x0, 0x0}

// pclntab118magic is the magic bytes used for binaries compiled with
// Go 1.18 and Go 1.19.
var pclntab118magic = []byte{0xf0, 0xff, 0xff, 0xff, 0x0, 0x0}

// pclntab120magic is the magic bytes used for binaries compiled with
// Go 1.20 and onwards.
var pclntab120magic = []byte{0xf1, 0xff, 0xff, 0xff, 0x0, 0x0}

// searchFileForPCLNTab will search the .rdata and .text section for the
// PCLN table. Note!! The address returned by this function needs to be
// adjusted by adding the image base address!!!
func searchFileForPCLNTab(f *pe.File) (uint32, []byte, error) {
for _, v := range []string{".rdata", ".text"} {
sec := f.Section(v)
if sec == nil {
continue
}
secData, err := sec.Data()
if err != nil {
continue
}
tab, err := searchSectionForTab(secData)
if err == ErrNoPCLNTab {
continue
}
// TODO: Switch to returning a uint64 instead.
addr := sec.VirtualAddress + uint32(len(secData)-len(tab))
return addr, tab, err
}
return 0, []byte{}, ErrNoPCLNTab
}
// keep sync with debug/gosym/pclntab.go
const (
gopclntab12magic uint32 = 0xfffffffb
gopclntab116magic uint32 = 0xfffffffa
gopclntab118magic uint32 = 0xfffffff0
gopclntab120magic uint32 = 0xfffffff1
)

// searchSectionForTab looks for the PCLN table within the section.
func searchSectionForTab(secData []byte) ([]byte, error) {
func searchSectionForTab(secData []byte, order binary.ByteOrder) ([]byte, error) {
// First check for the current magic used. If this fails, it could be
// an older version. So check for the old header.
MAGIC_LOOP:
for _, magic := range [][]byte{pclntab120magic, pclntab118magic, pclntab116magic, pclntab12magic} {
off := bytes.LastIndex(secData, magic)
MagicLoop:
for _, magic := range []uint32{gopclntab120magic, gopclntab118magic, gopclntab116magic, gopclntab12magic} {
bMagic := make([]byte, 6) // 4 bytes for the magic, 2 bytes for padding.
order.PutUint32(bMagic, magic)

off := bytes.LastIndex(secData, bMagic)
if off == -1 {
continue // Try other magic.
}
Expand All @@ -80,9 +51,9 @@ MAGIC_LOOP:
(buf[7] != 4 && buf[7] != 8) { // pointer size
// Header doesn't match.
if off-1 <= 0 {
continue MAGIC_LOOP
continue MagicLoop
}
off = bytes.LastIndex(secData[:off-1], magic)
off = bytes.LastIndex(secData[:off-1], bMagic)
continue
}
// Header match
Expand Down
Loading

0 comments on commit 4b7e5ff

Please sign in to comment.