From 4b7e5ff4be9287d42bf6401721e458d4aad25b8a Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 14 Feb 2024 04:53:08 +0800 Subject: [PATCH] feat: add brute force search fallback for elf --- elf.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++---- file.go | 13 +++++-- file_test.go | 6 +++- macho.go | 13 +++++-- moduledata.go | 51 +++++++++++++++++++++++----- pclntab.go | 63 ++++++++++------------------------ pe.go | 38 ++++++++++++++++++--- 7 files changed, 206 insertions(+), 71 deletions(-) diff --git a/elf.go b/elf.go index cf285d8..9dc94cd 100644 --- a/elf.go +++ b/elf.go @@ -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 } @@ -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 { diff --git a/file.go b/file.go index e8bcdda..54901d4 100644 --- a/file.go +++ b/file.go @@ -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() @@ -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. @@ -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) diff --git a/file_test.go b/file_test.go index df817a1..bd12bad 100644 --- a/file_test.go +++ b/file_test.go @@ -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") } @@ -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") } diff --git a/macho.go b/macho.go index 5337b7f..ea69000 100644 --- a/macho.go +++ b/macho.go @@ -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 } @@ -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 @@ -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) } diff --git a/moduledata.go b/moduledata.go index 2827680..a2a3a11 100644 --- a/moduledata.go +++ b/moduledata.go @@ -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()) @@ -310,9 +305,6 @@ search: goto invalidMD } - // Add the file handler. - md.fh = f - return md, nil invalidMD: @@ -320,6 +312,47 @@ invalidMD: 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 diff --git a/pclntab.go b/pclntab.go index 900f1e3..4078020 100644 --- a/pclntab.go +++ b/pclntab.go @@ -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. } @@ -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 diff --git a/pe.go b/pe.go index df0a919..015ddc4 100644 --- a/pe.go +++ b/pe.go @@ -59,6 +59,15 @@ type peFile struct { imageBase uint64 } +func (p *peFile) getSymbolValue(s string) (uint64, error) { + for _, sym := range p.file.Symbols { + if sym.Name == s { + return uint64(sym.Value), nil + } + } + return 0, fmt.Errorf("symbol %s not found", s) +} + func (p *peFile) getParsedFile() any { return p.file } @@ -67,16 +76,37 @@ func (p *peFile) getFile() *os.File { return p.osFile } -func (p *peFile) getPCLNTab() (*gosym.Table, error) { - addr, pclndat, err := searchFileForPCLNTab(p.file) +func (p *peFile) getPCLNTab(textStart uint64) (*gosym.Table, error) { + addr, pclndat, err := p.searchForPCLNTab() if err != nil { return nil, err } - pcln := gosym.NewLineTable(pclndat, uint64(p.file.Section(".text").VirtualAddress)+p.imageBase) + pcln := gosym.NewLineTable(pclndat, textStart) p.pclntabAddr = uint64(addr) + p.imageBase return gosym.NewTable(make([]byte, 0), pcln) } +// searchFileForPCLNTab will search the .rdata section for the +// PCLN table. +func (p *peFile) searchForPCLNTab() (uint32, []byte, error) { + sec := p.file.Section(".rdata") + if sec == nil { + return 0, nil, ErrNoPCLNTab + } + secData, err := sec.Data() + if err != nil { + return 0, nil, err + } + + tab, err := searchSectionForTab(secData, p.getFileInfo().ByteOrder) + if err != nil { + return 0, nil, err + } + + addr := sec.VirtualAddress + uint32(len(secData)-len(tab)) + return addr, tab, err +} + func (p *peFile) Close() error { err := p.file.Close() if err != nil { @@ -107,7 +137,7 @@ func (p *peFile) moduledataSection() string { } func (p *peFile) getPCLNTABData() (uint64, []byte, error) { - b, d, e := searchFileForPCLNTab(p.file) + b, d, e := p.searchForPCLNTab() return p.imageBase + uint64(b), d, e }