Having fun with PE files and GoLang

2 minute read Published:

Opening PE files with GoLang


New blog design, new post.

Today I will show how GoLang interacts with PE files in a generic example. You could look further into the native module here or even check its source code here. I do recommend reading it, I am using some bits of code extracted directly from the module source.

Here you go.

package main

import (
	"fmt"
	"debug/pe"
	"os"
	"io"
	"encoding/binary"
)

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: petest pe_file")
		os.Exit(1)
	}

	file := ioReader(os.Args[1])
	f, err := pe.NewFile(file)
	check(err)
	
	var sizeofOptionalHeader32 = uint16(binary.Size(pe.OptionalHeader32{}))
	var sizeofOptionalHeader64 = uint16(binary.Size(pe.OptionalHeader64{}))
	
	var dosheader [96]byte	
	var sign [4]byte
	file.ReadAt(dosheader[0:], 0)
	var base int64
	if dosheader[0] == 'M' && dosheader[1] == 'Z' {
		signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:]))
		//var sign [4]byte
		file.ReadAt(sign[:], signoff)
		if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) {
			fmt.Printf("Invalid PE File Format.\n")
		}
		base = signoff + 4
	} else {
		base = int64(0)
	}

	sr := io.NewSectionReader(file, 0, 1<<63-1)
	sr.Seek(base, os.SEEK_SET)
	binary.Read(sr, binary.LittleEndian, &f.FileHeader)

	var oh32 pe.OptionalHeader32
	var oh64 pe.OptionalHeader64
	var x86_x64 string
	var magicNumber uint16
	
	switch f.FileHeader.SizeOfOptionalHeader {
		case sizeofOptionalHeader32:
			binary.Read(sr, binary.LittleEndian, &oh32)
			if oh32.Magic != 0x10b { // PE32
				fmt.Printf("pe32 optional header has unexpected Magic of 0x%x", oh32.Magic)
			}
			magicNumber = oh32.Magic
			x86_x64 = "x86"

		case sizeofOptionalHeader64:
			binary.Read(sr, binary.LittleEndian, &oh64)
			if oh64.Magic != 0x20b { // PE32+
				fmt.Printf("pe32+ optional header has unexpected Magic of 0x%x", oh64.Magic)
			}
			magicNumber = oh64.Magic
			x86_x64 = "x64"
	}
	
	var isDLL bool
	if (f.Characteristics & 0x2000) == 0x2000 {
		isDLL = true
	} else if (f.Characteristics & 0x2000) != 0x2000 {
		isDLL = false
	}
	
	var isSYS bool
	if (f.Characteristics & 0x1000) == 0x1000 {
		isSYS = true
	} else if (f.Characteristics & 0x1000) != 0x1000 {
		isSYS = false
	}
		
	f.Close() //close file handle
	
	
	fmt.Printf("OptionalHeader: %#x\n", f.OptionalHeader)
	fmt.Printf("DLL File: %t\n", isDLL)
	fmt.Printf("SYS File: %t\n", isSYS)
	fmt.Printf("Base: %d\n", base)
	fmt.Printf("File type: %c%c\n", sign[0],sign[1])
	fmt.Printf("dosheader[0]: %c\n", dosheader[0])
	fmt.Printf("dosheader[1]: %c\n", dosheader[1])
	fmt.Printf("MagicNumber: %#x (%s)\n", magicNumber, x86_x64)

}

Compile with: go build -i pe_test.go

Usage: petest file-to-analyze.ext

The expected output will be something like this:

OptionalHeader: &{0x20b 0xd7000 0x400 0x177200 0x10 c4000 0x9204}
DLL File: false
SYS File: false
Base: 252
File type: PE
dosheader[0]: M
dosheader[1]: Z
MagicNumber: 0x20b (x64)

I am working on a similar post for ELF files too, should be up soon.

Cheers