gopep (Go Lang Portable Executable Parser) is Python script for extracting attributes from PE executables compiled in Go. This repo is part of a personal project for learning about executables compiled Go. For anyone else wanting to learn about executable compiled in Go, I would recommend reading through the links in the resources section.
gopep can be viewed for viewing contents of fields/structures extracted from Go's ModuleData.
functab
contains function symbol names and their start offset.
- instance.functab
- instance.symbols (easier to remember)
Example Output
(4198400, b'go.buildid'), (4198512, b'internal/cpu.Initialize')
filetab
contains all of the file paths for the source code (path/foo.go
).
- instance.filetab
Example Output
[b'<autogenerated>', b'/usr/local/go/src/runtime/internal/sys/intrinsics_386.s', b'/usr/local/go/test/helloworld.go']
The last file path is commonly a path that is not associated with Go libraries but the path of the developers source code. For example, /usr/local/go/test/helloworld.go
is the source path for a "hello, world" Go binary. Other source files likely from the same source path are found using the difflib library and the SequenceMatcher function. These can be found by using the following.
- instance.go_base_paths
Example Output
[b'/usr/local/go/test/helloworld.go']
- instance.itab_sym
Not present if binary is stripped.
Example Output
[b'go.itab.*os.File,io.Writer', b'go.itab.*errors.errorString,error', b'go.itab.*reflect.rtype,reflect.Type', b'go.itab.*fmt.pp,fmt.State', b'go.itab.*internal/reflectlite.rtype,internal/reflectlite.Type', b'go.itab.*internal/fmtsort.SortedMap,sort.Interface', b'go.itab.*os.PathError,error', b'go.itab.syscall.Errno,error', b'go.itab.runtime.errorString,error', b'go.itab.*syscall.DLLError,error', b'go.itab.*internal/poll.TimeoutError,error']
- instance.symtab_symbols
Example Output
[b'runtime.text', b'runtime.etext', b'main..inittask', b'internal/cpu.X86',
- instance.stripped
- True or False
- instance.packeed
- True or False
- Only checks for UPX by comparing section names.
- yeah, it sucks but I'm not caring much about packers detection right now.
- instance.hash_sys_all
- instance.hash_sys_main
- instance.hash_sys_nomain
- instance.hash_itabs
- instance.hash_file_paths
Attributes from Go binaries can be hashed and used for clustering. This technique is the same as Imphash (Import Hashing), which hashes blocks of string using MD5. This form of hashing is trivial to break but is still interesting to experiment with. GoPeP hashes based off the following
- all symbol/function names
- main symbols/functions
- non-main symbols/functions
- source code paths (functab)
/usr/local/go/test/helloworld.go
- itabs (if present)
gopep can be used as a class of executed as a script. The following command line options are available.
python gopep.py -h
usage: gopep.py [-h] [-c C_DIR] [-e E_FILE] [-x EA_DIR] [-v IN_FILE] [-m MD_FILE] [-t T_FILE] [-ev ET_FILE]
gopep Go Portable Executable Parser
optional arguments:
-h, --help show this help message and exit
-c C_DIR, --cluster C_DIR
cluster directory of files
-e E_FILE, --export E_FILE
export results of file to JSON
-x EA_DIR, --export_all EA_DIR
export results of directory to JSONs
-v IN_FILE, --version IN_FILE
print version
-m MD_FILE, --module-data MD_FILE
print module data details
-t T_FILE, --triage T_FILE
triage file, print interesting attributes
-ev ET_FILE, --everything ET_FILE
print EVERYTHING!
Go logger is a simple function hooker for Go executables. Using libptrace (thank you 0vercl0k for the recommendation), it sets a breakpoint on the main.main
then sets a breakpoint on all functions. The function address and name is extracted from functab
.
The API addresses and function names are extracted using gopep.py
with the -st
argument. This arugment creates a json
with the string .json
appended to the executable file name.
Command line arguments %python3.7%\python.exe go_logger.py go.exe go.json
. An example command line can be seen below.
C:\Users\null\AppData\Local\Programs\Python\Python37\python.exe go_hook.py C:\Use
rs\null\Documents\repo\gopep\test\go-hello-stripped.exe C:\Users\null\Documents\repo\gopep\test\go-hello-stripped.exe.js
on
C:\Users\null\Documents\repo\libptrace>C:\Users\null\AppData\Local\Programs\Python\Python37\python.exe go_hook.py C:\Use
rs\null\Documents\repo\gopep\test\go-hello-stripped.exe C:\Users\null\Documents\repo\gopep\test\go-hello-stripped.exe.js
on
[15256] Module ntdll loaded at 0x7ffb99da0000
[15256] Module ntdll loaded at 0x77a30000
[15256] Created thread with tid 11528
[15256] Module wow64 loaded at 0x7ffb98080000
[15256] Module wow64win loaded at 0x7ffb992a0000
[15256] Attached
[15256] BreakPoint Set at 0x00451840
[15256] Module wow64cpu loaded at 0x77a20000
[15256] Breakpoint
[15256] Thread with tid 11528 exited
[15256] Module kernel32 loaded at 0x75c50000
[15256] Module KernelBase loaded at 0x76b80000
[15256] Breakpoint
[15256] Module advapi32 loaded at 0x75b80000
[15256] Module msvcrt loaded at 0x76a60000
[15256] Created thread with tid 6972
[15256] Module sechost loaded at 0x752b0000
[15256] Module rpcrt4 loaded at 0x76f00000
[15256] Module sspicli loaded at 0x751f0000
[15256] Module cryptbase loaded at 0x751e0000
[15256] Module bcryptprimitives loaded at 0x77110000
[15256] Created thread with tid 15136
[15256] Created thread with tid 14576
[15256] Module winmm loaded at 0x74f10000
[15256] Module winmmbase loaded at 0x006a0000
[15256] Module winmmbase loaded at 0x749d0000
[15256] Module winmmbase unloaded
[15256] Module winmmbase loaded at 0x00860000
[15256] Module winmmbase unloaded
[15256] Module cfgmgr32 loaded at 0x75d50000
[15256] Module ucrtbase loaded at 0x768e0000
[15256] Module ws2_32 loaded at 0x76b20000
[15256] Module powrprof loaded at 0x773e0000
[15256] Module umpdc loaded at 0x77a10000
[15256] Created thread with tid 14972
[15256] Created thread with tid 14472
[15256] Created thread with tid 13372
[15256] Created thread with tid 2728
[15256] Func=runtime.printlock, IP=0x0042a390, Frame=0x11028fb8, Base=0x00000038, Return=0x0045185a
[15256] Func=runtime.lock, IP=0x00407250, Frame=0x11028fac, Base=0x00000038, Return=0x0042a3e5
[15256] Func=runtime/internal/atomic.Casuintptr, IP=0x00401fd0, Frame=0x11028f84, Base=0x00000038, Return=0x004072ac
[15256] Func=runtime/internal/atomic.Cas, IP=0x00401fb0, Frame=0x11028f84, Base=0x00000038, Return=0x004072ac
[15256] Func=runtime.printstring, IP=0x0042ac30, Frame=0x11028fb8, Base=0x00000038, Return=0x00451870
[15256] Func=runtime.gwrite, IP=0x0042a460, Frame=0x11028f94, Base=0x00000038, Return=0x0042ac99
[15256] Func=runtime.recordForPanic, IP=0x0042a270, Frame=0x11028f7c, Base=0x00000038, Return=0x0042a49d
[15256] Func=runtime.printlock, IP=0x0042a390, Frame=0x11028f64, Base=0x00000038, Return=0x0042a28e
[15256] Func=runtime/internal/atomic.Load, IP=0x00401f70, Frame=0x11028f64, Base=0x00000038, Return=0x0042a29c
[15256] Func=runtime.memmove, IP=0x0044d2c0, Frame=0x11028f64, Base=0x0000000d, Return=0x0042a35f
[15256] Func=runtime.printunlock, IP=0x0042a400, Frame=0x11028f64, Base=0x0000000d, Return=0x0042a36d
[15256] Func=runtime.writeErr, IP=0x00448160, Frame=0x11028f7c, Base=0x00000000, Return=0x0042a4ea
[15256] Func=runtime.write, IP=0x00440680, Frame=0x11028f68, Base=0x00000000, Return=0x00448195
[15256] Func=runtime.write1, IP=0x00425d10, Frame=0x11028f54, Base=0x00000000, Return=0x0044069f
[15256] Func=runtime.stdcall1, IP=0x00426930, Frame=0x11028f28, Base=0x00000000, Return=0x00425e09
[15256] Func=runtime.stdcall, IP=0x00426850, Frame=0x11028f1c, Base=0x00000000, Return=0x00426963
[15256] Func=runtime.asmcgocall, IP=0x0044c400, Frame=0x11028f04, Base=0x00000000, Return=0x004268c1
[15256] Func=gosave, IP=0x0044c3c0, Frame=0x11028f00, Base=0x004b62a0, Return=0x0044c436
[15256] Func=runtime.asmstdcall, IP=0x0044d560, Frame=0x0019feac, Base=0x004b62a0, Return=0x0044c45e
[15256] Func=runtime.stdcall5, IP=0x00426a30, Frame=0x11028f28, Base=0x0000000a, Return=0x00425dbf
[15256] Func=runtime.stdcall, IP=0x00426850, Frame=0x11028f1c, Base=0x0000000a, Return=0x00426a63
[15256] Func=runtime.asmcgocall, IP=0x0044c400, Frame=0x11028f04, Base=0x0000000a, Return=0x004268c1
[15256] Func=gosave, IP=0x0044c3c0, Frame=0x11028f00, Base=0x004b62a0, Return=0x0044c436
[15256] Func=runtime.asmstdcall, IP=0x0044d560, Frame=0x0019feac, Base=0x004b62a0, Return=0x0044c45e
[15256] Func=runtime.printunlock, IP=0x0042a400, Frame=0x11028fb8, Base=0x0019feac, Return=0x00451875
[15256] Func=runtime.unlock, IP=0x00407430, Frame=0x11028fb0, Base=0x0019feac, Return=0x0042a44c
[15256] Func=runtime/internal/atomic.Loaduintptr, IP=0x00401ff0, Frame=0x11028f94, Base=0x0019feac, Return=0x00407468
[15256] Func=runtime/internal/atomic.Load, IP=0x00401f70, Frame=0x11028f94, Base=0x0019feac, Return=0x00407468
[15256] Func=runtime/internal/atomic.Casuintptr, IP=0x00401fd0, Frame=0x11028f94, Base=0x0019feac, Return=0x0040748d
[15256] Func=runtime/internal/atomic.Cas, IP=0x00401fb0, Frame=0x11028f94, Base=0x0019feac, Return=0x0040748d
[15256] Func=runtime/internal/atomic.Load, IP=0x00401f70, Frame=0x11028fc4, Base=0x0019feac, Return=0x0042b1ff
[15256] Func=runtime/internal/atomic.Load, IP=0x00401f70, Frame=0x11028fc4, Base=0x0019feac, Return=0x0042b24e
[15256] Func=runtime.exit, IP=0x00425cc0, Frame=0x11028fc4, Base=0x0019feac, Return=0x0042b262
[15256] Func=runtime.lock, IP=0x00407250, Frame=0x11028fb4, Base=0x0019feac, Return=0x00425cd1
[15256] Func=runtime/internal/atomic.Casuintptr, IP=0x00401fd0, Frame=0x11028f8c, Base=0x0019feac, Return=0x004072ac
[15256] Func=runtime/internal/atomic.Cas, IP=0x00401fb0, Frame=0x11028f8c, Base=0x0019feac, Return=0x004072ac
[15256] Func=runtime/internal/atomic.Store, IP=0x00402150, Frame=0x11028fb4, Base=0x0019feac, Return=0x00425ce7
[15256] Func=runtime.stdcall1, IP=0x00426930, Frame=0x11028fb4, Base=0x0019feac, Return=0x00425cfd
[15256] Func=runtime.stdcall, IP=0x00426850, Frame=0x11028fa8, Base=0x0019feac, Return=0x00426963
[15256] Func=runtime.asmcgocall, IP=0x0044c400, Frame=0x11028f90, Base=0x0019feac, Return=0x004268c1
[15256] Func=gosave, IP=0x0044c3c0, Frame=0x11028f8c, Base=0x004b62a0, Return=0x0044c436
[15256] Func=runtime.asmstdcall, IP=0x0044d560, Frame=0x0019feac, Base=0x004b62a0, Return=0x0044c45e
[15256] Thread with tid 14576 exited
[15256] Thread with tid 15136 exited
[15256] Thread with tid 6972 exited
[15256] Thread with tid 2728 exited
[15256] Func=runtime.nanotime1, IP=0x0044d930, Frame=0x10f0fee0, Base=0x1102c1e0, Return=0x0043407d
[15256] Thread with tid 13372 exited
[15256] Thread with tid 14472 exited
[15256] Thread with tid 14972 exited
[15256] exited
Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
OOS=windows GOARCH=amd64 go build -o ~/hello-stripped-64.exe helloworld.go
OOS=windows GOARCH=386 go build -o ~/hello-stripped-64.exe helloworld.go
OOS=windows GOARCH=amd64 go build -o ~/hello-stripped-64.exe -ldflags="-s -w" helloworld.go
OOS=windows GOARCH=386 go build -o ~/hello-stripped-64.exe -ldflags="-s -w" helloworld.go
Via Say Hello to Moduledata Note
- moduledata can be located in the
.text
or.data
sections - Is present in stripped binaries. As long as "-s -w" is correct for compiling stripped binaries.
- Defined in
symtab.go
pclntable
a table that holds mappings between source code line numbers and the program counter.- Used for stack traces. Not removed if stripped.
- Includes the full file path and name of the source file at compile time and the name of the function
ftab
orfunctab
short for function tab. Used by the runtime functionFuncForPC
- PTR size is stored in the binary.
func moduledataverify1(datap *moduledata) {
// See golang.org/s/go12symtab for header: 0xfffffffb,
// two zero bytes, a byte giving the PC quantum,
// and a byte giving the pointer width in bytes.
Don't need to check the bit version.
.text:00520D60 runtime_pclntab dd 0FFFFFFFBh ; DATA XREF: .data:runtime_firstmoduledata↓o
.text:00520D64 db 0
.text:00520D65 db 0
.text:00520D66 db 1
.text:00520D67 db 4
- Symbol table information about.
- Functions and global variables.
- Regardless of
-g
compile switch. - Every relocatable object file.
- Has a symbol table in
.symtab
. - The symbol table in
.symtab
. - No entries for local variables.
- The symbol table inside a compiler.
- Does have entries for local variables.
n_name
has another check. If the first four bytes are null (00 00 00 00) then the last four byte are an offset into the string table.- The start of the string table can be found by
FileHeader.PointeToSybolTable + (FileHeader.NumberOfSymbols * 18)
. - In ELF executables there is another section named
.strtab
that appears to be similar.
stucture of coff table, well kind of
{
char n_name[8]; /* Symbol Name */
long n_value; /* Value of Symbol */
short n_scnum; /* Section Number */
unsigned short n_type; /* Symbol Type */
char n_sclass; /* Storage Class */
char n_numaux; /* Auxiliary Count */
}
source: https://wiki.osdev.org/COFF#String_Table
- Stripped binaries for Go Windows executables can be identified by traversing the section names.
- If "/" is present in a section name then the binary is not stripped.
- If the section
.symtab
is filled with null (\x00
) bytes the binary is stripped.
- Dissecting Go Binaries
- Go: Overview of the Compiler
- Go compiler internals: adding a new statement to Go - Part 1
- Go compiler internals: adding a new statement to Go - Part 2
- Reversing GO binaries like a pro
- How a Go Program Compiles down to Machine Code
- Analyzing Golang Executables
- Go Reverse Engineering Tool Kit
- go-internals book
- Reconstructing Program Semantics from Go Binaries
- The Go low-level calling convention on x86-64
- Golang Internals (1 of 6)
- Go Series by Joakim Kennedy
- Reverse Engineering Go, Part I
- Yet Another Golang binary parser for IDAPro