Now I will show how GoLang interacts with ELF files in a generic example. You could look further into the native module here. I do recommend reading it, I am using some bits of code extracted directly from the module source.
It is basically the same idea as the PE, similar module. You can extend it depending on your needs.
Here you go.
package main
import (
"fmt"
"io"
"os"
"debug/elf"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func ioReader(file string) io.ReaderAt {
r, err := os.Open(file)
check(err)
return r
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: elftest elf_file")
os.Exit(1)
}
f := ioReader(os.Args[1])
_elf, err := elf.NewFile(f)
check(err)
// Read and decode ELF identifier
var ident [16]uint8
f.ReadAt(ident[0:], 0)
check(err)
if ident[0] != '\x7f' || ident[1] != 'E' || ident[2] != 'L' || ident[3] != 'F' {
fmt.Printf("Bad magic number at %d\n", ident[0:4])
os.Exit(1)
}
var arch string
switch _elf.Class.String() {
case "ELFCLASS64":
arch = "64 bits"
case "ELFCLASS32":
arch = "32 bits"
}
var mach string
switch _elf.Machine.String() {
case "EM_AARCH64":
mach = "ARM64"
case "EM_386":
mach = "x86"
case "EM_X86_64":
mach = "x86_64"
}
fmt.Printf("File Header: ")
fmt.Println(_elf.FileHeader)
fmt.Printf("ELF Class: %s\n", arch)
fmt.Printf("Machine: %s\n", mach)
fmt.Printf("ELF Type: %s\n", _elf.Type)
fmt.Printf("ELF Data: %s\n", _elf.Data)
fmt.Printf("Entry Point: %d\n", _elf.Entry)
fmt.Printf("Section Addresses: %d\n", _elf.Sections)
}
Compile with: go build -i elftest.go
Usage: ./elftest file-to-analyze
The expected output will be something like this:
File Header: {ELFCLASS64 ELFDATA2LSB EV_CURRENT ELFOSABI_NONE 0 LittleEndian ET_EXEC EM_X86_64 4200880}
ELF Class: 64 bits
Machine: x86-64
ELF Type: ET_EXEC
ELF Data: ELFDATA2LSB
Entry Point: 4200880
Section Addresses: [826814817600 826814817696 826814817792 826814817888 826814817984 826814818080 826814818176]
That’s it. Simple enough!
Cheers