Introduction to analysing Go binaries
Golang is gaining popularity by malware authors, and more golang based malware is being found in the wild. It is also one of my favourite programming languages, especially for all network related applications, for the reasons of:
- the language syntax itself is simple, but you can do everything with it
- binaries are being compiled statically, no need to install libraries
- support for many operating systems and architectures
- statically typed
- goroutines (light weight threads)
- channels (pipes using which goroutines communicate)
And my most important reason is that code I've created years ago, still compiles without the need to update or refactor the code to the newer compilers and language versions.
Due the popularity of Golang by malware authors, we need more often to analyse these binaries and they are a bit different. Let me give you some primers.
Let's start first with a Hello World Golang application first, to give you an idea about the language and the structure.
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
It starts with a package name, all files in the same folder will have the same package name. In this case main. Next we'll configure the imports, in this case the fmt library. Go is very struct and only allows imports that are being used. The main entry point is always called main. Now the code can be compiled for different architectures using the following command:
$ GOOS=linux GOARCH=amd64 go build -o hello ./hello.go
Go compiled binaries are large, the Hello World example in this case is 1.9M. Now we know the basics, we can start analysing a real malware named sshd, which someone delivered kindly to our honeypots. Because of the large binaries, upx is being used often to suppress the file size. By default upx compress files can be decompressed using:
$ upx -d ./sshd
When loading the binary into your favourite disassembler, you'll find many functions and strings. Most of those functions and strings are from default Go packages. If you have an address of a function start, the golang tool addr2line will come in handy. Addr2line uses the gopclntab section to retrieve data about the original function name, source file and locations. You'll probably encounter cases where the location of the source file contains the username in its path.
$ echo "0x0062cd40" | go tool addr2line ./sshd
main.main
/root/go/src/sshcrack/sshcrack.go:184
There is also objdump which will output symbols and source locations together with assembly code if possible:
$ go tool objdump -s main ./vv
...
vv.go:321 0x6128e5 e8b6e9ffff CALL main.post(SB)
vv.go:321 0x6128ea 488b442418 MOVQ 0x18(SP), AX
vv.go:321 0x6128ef 488b4c2420 MOVQ 0x20(SP), CX
vv.go:322 0x6128f4 48890424 MOVQ AX, 0(SP)
vv.go:322 0x6128f8 48894c2408 MOVQ CX, 0x8(SP)
vv.go:322 0x6128fd e86ee7ffff CALL main.rsaDecrypt(SB)
vv.go:322 0x612902 488b442410 MOVQ 0x10(SP), AX
vv.go:322 0x612907 488b4c2418 MOVQ 0x18(SP), CX
vv.go:323 0x61290c 48890424 MOVQ AX, 0(SP)
vv.go:323 0x612910 48894c2408 MOVQ CX, 0x8(SP)
vv.go:323 0x612915 e896feffff CALL main.jsonload(SB)
vv.go:323 0x61291a 488d742410 LEAQ 0x10(SP), SI
vv.go:323 0x61291f 488d7c2440 LEAQ 0x40(SP), DI
...
Strings in the binary are not null terminated, but instead all concatenated to long strings. When strings are being referenced to, a following instruction will load the length. The instruction lea rax, ... loads the string reference, where the following instructions defines the length of the string to load, in this case 7 and makes it the string /bin/sh. In later Golang version this has been changed, as I believe, which I'll get back to in a later post.
Objects are being instantiated using runtime_newobject, taking a structure pointer as argument. The definition of the struct is being defined in the .typelink section of the binary.
Version information in the binary can be found by searching for go1, which function runtime_schedinit references to.
The call to sym.runtime.morestack_noctxt will resize the stack, as Go makes use of resizable stacks, then it will return to the start of the function, this pattern marks also the end and start of the function.
This was just a quick overview of Golang binaries, more to follow. Please share your ideas, comments and/or insights, with me via social media, @remco_verhoef or email, remco.verhoef at dutchsec dot com. Have a great day!
Remco Verhoef (@remco_verhoef)
ISC Handler - Founder of DutchSec
PGP Key
Comments