While reading Hands On System Programming with Go, I discovered the debug/pe package. To learn more about using Go I decided to spend a few days building a static forensic tool centered around use of this package. The debug/pe package provides functions that make analyzing a PE file easier. A glimpse of promising functions include: Open, ImportedLibraries, FileHeader, OpentionHeader32/64, and StringTable. Limiting myself to a span of a week, I’m reporting on functions I’ve applied to penut, a simple PE file static analysis tool, and what the next steps are to build upon the application.

The debug/pe Package

Load PE file for analysis

pe.Open() will load your executable and prep it for use as type *File. A File has the following struct:

type File struct {
    OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64; added in Go 1.3
    Sections       []*Section
    Symbols        []*Symbol    // COFF symbols with auxiliary symbol records removed; added in Go 1.1
    COFFSymbols    []COFFSymbol // all COFF symbols (including auxiliary symbol records); added in Go 1.8
    StringTable    StringTable // Go 1.8
    // contains filtered or unexported fields

This is significant because it suggests that bare details of the executable are already available to the operator, ready to print to screen. Here is an example of pe.Open within the content of the application.

	pfile, err := pe.Open(os.Args[1])
	if err != nil {
		fmt.Fprintf(os.Stderr, "pe.Open:%s\n", err)
	defer pfile.Close()


As shown in the File struct above, we have access to Sections []*Section. This suggests we should be able to output some information related to PE sections. Sections are a basic unit of code or data within a Portable Executable (PE) or Common Object File Format (COFF) file. Section contents vary by section type; each section could occupied by a function or an object, and images make up multiple sections. Some sections serve special purposes, such as .idata, which stores all imported symbols.

func printSections(f *pe.File) {
	for _, s := range f.Sections {
		fmt.Printf("%s\n", s.Name)
		fmt.Printf("%0#8x %s\n", s.VirtualSize, "Virtual Size")
		fmt.Printf("%0#8x %s\n", s.VirtualAddress, "Virtual Address")
		fmt.Printf("%0#8x %s\n", s.Size, "Size")
		fmt.Printf("%0#8x %s\n", s.Offset, "Offset")
		fmt.Printf("%0#8x %s\n", s.PointerToRelocations, "Pointer To Relocations")
		fmt.Printf("%0#8x %s\n", s.PointerToLineNumbers, "Pointer to Line Numbers")
		fmt.Printf("%0#8x %s\n", s.NumberOfRelocations, "Number of Relocations")
		fmt.Printf("%0#8x %s\n", s.NumberOfLineNumbers, "Number of Line Numbers")
		fmt.Printf("%0#8x %s\n", s.Characteristics, "Characteristics")

The beautiful code in the snippet above was made avaliable by joesephspurrier. Here is a portion of the resulting output:

$go run main.go main.exe

0x000003fc Virtual Size
0x001fb000 Virtual Address
0x00000400 Size
0x001dd000 Offset
0x00000000 Pointer To Relocations
0x00000000 Pointer to Line Numbers
0x00000000 Number of Relocations
0x00000000 Number of Line Numbers
0xc0000040 Characteristics

0x0000d6a8 Virtual Size
0x001fc000 Virtual Address
0x0000d800 Size
0x001dd400 Offset
0x00000000 Pointer To Relocations
0x00000000 Pointer to Line Numbers
0x00000000 Number of Relocations
0x00000000 Number of Line Numbers
0x42000040 Characteristics

Next Steps

Right now, the main.go file must be modified to obtain any information, not much of it actionable. If I were to continue to work on penut I would like to output the results to a report and instead print a summary out after running penut. As I continued to experiment with the available types, structs, and functions provided by debug/pe I found that I would need to better famliarize myself with the Go language in order to digest and parse the information provided by the pe package.

File Header

We can, for instance, pull File Header information from the new File instance pfile. The code below is an example of a function that would read in pfile and print out the File Header information.

func printFileHeader(f *pe.File) {
	a := f.FileHeader
	fmt.Print("File Header: ")

And the resulting output:

$go run main.go main.exe 
File Header: {332 15 0 2010112 3392 224 770}

Looking at the above output of printFileHeader() we see the value of the pfile OptionalHeader interface{}. Microsoft Docs writes, “the PE file header consists of a Microsoft MS-DOS stub, the PE signature, the COFF file header, and an optional header. A COFF object file header consists of a COFF file header and an optional header.” The output of f.FileHeader consists of 7 values, inconsistent with the 4 values aforementioned (MS-DOS stub, PE signature, COEFF file header, optional header). So what are we seeing here? I’m not yet sure.

String Table

As another example, the StringTable attribute prints out what seems to be a LONG string of bytes.

func printStringTable(f *pe.File) {
a := f.StringTable

StringTable Bytes Output

This long string of bytes can be cast into a string for human reading.

func printStringTable(f *pe.File) {
    a := string(f.StringTable)

StringTable Bytes Cast to String

The long string appears to be deliniated by a period .. By itself, the data is not yet actionable, but you can look over the strings output a little easier by replacing the periods with newlines, as shown in the code and output below.

func printStringTable(f *pe.File) {
	a := string(f.StringTable)
	b := strings.Replace(a, ".", "\n", -1)

StringTable String Parsed [ … ] StringTable String Parsed


While penut is far from being a functional static analysis tool, it was fun to experiment with the Go debug/pe package for a few days. If you would like to see a working solution check out soluwalana’s go-pefile github repo.

Thanks for reading! I hope you found the information educational. For even more information, see the following references: