Last Updated: 2019-03-22 16:13:46 UTC
by Remco Verhoef (Version: 1)
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.
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:
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:
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.
There is also objdump which will output symbols and source locations together with assembly code if possible:
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!
If you have more information or corrections regarding our diary, please share.