elf.go (153 lines of code) (raw):
// Copyright ©2022 Elastic N.V. All rights reserved.
// Copyright ©2021 Dan Kortschak. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package toutoumomoma
import (
"debug/elf"
"debug/gosym"
"io"
"strings"
)
type elfFile struct {
r io.ReaderAt
objFile *elf.File
}
func openELF(r io.ReaderAt) (*elfFile, error) {
objFile, err := elf.NewFile(r)
if err != nil {
return nil, err
}
return &elfFile{r: r, objFile: objFile}, nil
}
func (f *elfFile) Close() error {
f.objFile = nil
if c, ok := f.r.(io.Closer); ok {
return c.Close()
}
return nil
}
func (f *elfFile) isGoExecutable() (ok bool, err error) {
for _, section := range f.objFile.Sections {
switch section.Name {
case ".gosymtab", ".gopclntab", ".go.buildinfo":
return true, nil
}
}
return false, nil
}
func (f *elfFile) hasBuildID() (ok bool, err error) {
sect := f.objFile.Section(".note.go.buildid")
if sect == nil {
return false, nil
}
_, err = sect.Data()
return err == nil, err
}
func (f *elfFile) hasRealFiles() (ok bool, err error) {
tab, err := f.pclnTable()
if err != nil {
return false, err
}
symbols, err := f.objFile.Symbols()
if err != nil {
if err == elf.ErrNoSymbols {
return false, nil
}
return false, err
}
for _, sym := range symbols {
if sym.Name != "main.main" {
continue
}
file, _, _ := tab.PCToLine(sym.Value)
if file == "??" {
return false, nil
}
}
return true, nil
}
func (f *elfFile) importedSymbols() ([]string, error) {
imps, err := f.objFile.ImportedSymbols()
if err != nil && err != elf.ErrNoSymbols {
return nil, err
}
if len(imps) == 0 {
return nil, nil
}
imports := make([]string, len(imps))
for i, imp := range imps {
imports[i] = strings.ToLower(imp.Library + "." + imp.Name)
}
return imports, nil
}
func (f *elfFile) goSymbols(stdlib bool) ([]string, error) {
syms, err := f.objFile.Symbols()
if err != nil {
if err == elf.ErrNoSymbols {
err = nil
}
return nil, err
}
if len(syms) == 0 {
return nil, nil
}
tab, err := f.pclnTable()
if err != nil {
return nil, err
}
imports := make([]string, 0, len(syms))
for _, sym := range syms {
switch sym.Section {
case elf.SHN_UNDEF, elf.SHN_COMMON:
continue
}
if strings.HasPrefix(sym.Name, "type..") {
continue
}
if !stdlib && isStdlib(sym.Name, sym.Value, tab) {
continue
}
i := int(sym.Section)
if i < 0 || i >= len(f.objFile.Sections) {
continue
}
sect := f.objFile.Sections[i]
if sect.Flags&(elf.SHF_ALLOC|elf.SHF_EXECINSTR) == elf.SHF_ALLOC|elf.SHF_EXECINSTR {
imports = append(imports, sym.Name)
}
}
if len(imports) == 0 {
return nil, nil
}
return imports, nil
}
func (f *elfFile) pclnTable() (*gosym.Table, error) {
textStart, symtab, pclntab, err := f.pcln()
if err != nil {
return nil, nil
}
return gosym.NewTable(symtab, gosym.NewLineTable(pclntab, textStart))
}
func (f *elfFile) sectionStats() ([]Section, error) {
s := make([]Section, len(f.objFile.Sections))
for i, sect := range f.objFile.Sections {
var (
h, sigma float64
fileSize uint64
)
// Don't read sect.Size zero bytes for SHT_NOBITS,
// and don't use written size of section in that case
// since it may be lying.
if sect.Type != elf.SHT_NOBITS {
fileSize = sect.FileSize
var err error
h, sigma, err = streamEntropy(sect.Open())
if err != nil {
return nil, err
}
}
s[i] = Section{
Name: sect.Name,
Size: sect.Size,
FileSize: fileSize,
Entropy: h,
VarEntropy: sigma,
Flags: uint32(sect.Flags),
}
}
return s, nil
}