ANATOMY OF A GOPHER || BINARY ANALYSIS OF GO BINARIES HEX0PUNK / ALEX USECHE
WHO IS THIS GUY? hex0punk Father & husband Senior AppSec Consultant at nVisium Fan of Go and Rust Likes playing with binaries
WHAT ARE WE DOING?
Learning about what makes go binaries different than C and C++ binaries Identifying techniques for recognizing and conducting analysis of go binaries. Tips for finding vulnerabilities in go binaries. Identifying common patterns found in go binaries Learning about protections that can be added to go binaries
ONCE UPON A TIME Found go binaries during an IoT assessment Wait, this isn't C or C++! This is Go! Now what?
THE GO ASSEMBLER
The go compiler is based on the plan9 compiler Semi-abstract instruction set Pseudo-Assembly Not a direct representation of the underlying machine (i.e. a MOV may be a LD) It also introduces a set of pseudo registers
GO TOOL OBJDUMP go tool objdump -s RADARE2
go tool objdump vs. radare2
· Use both! · Differences are easy to understand: · A big advantage of using go tool objdump is that you get line numbers in code. This can be help you group instructions by operations
true for MachO Got ROP?
THE GO ASSEMBLER DEFAULT PROTECTIONS Stack Protection: export CGO_LDFLAGS='fstack-protector' Stripped: GOOS=linux go build -ldflags="s -w" PIE: export GOFLAGS='-buildmode=pie' Use a tool like UPX (https://upx.github.io/) to strip function names and reduce size
SEARCHING FOR
rabin2 -zz go-bin-goo | grep 1.__TEXT.__rodata | grep intruder
STRINGS
SEARCHING FOR STRINGS · Go does not store null terminated strings. · Strings are clumped together, while keeping a separate table with length information. · This can make it difficult to look for string cross-references · We can use a projects like https://github.com/carvesystems/gostringsr2 to parse strings · When working with MachO binaries, you'd have to list strings from .rodata or entire binary · rabin2 -zz · izz using r2
SEARCHING FOR FUNCTIONS
· Most of the times, functions are easy to find, even in stripped binaries. · Functions follow with the following naming conventions: package.function package.receiver struct.function Package.receiver struct.function
THE EASY PART FINDING MAIN
· Go routines have small stacks by default (2 kibibyte = 1024 bytes stack) · Many goroutines will calls morestack to grow the stack (in powers of 2) as needed using stack copying · This is called because go can't be sure the function will outgrow the stack (i.e. recursive functions) given non-deterministic goroutines · When this occurs, stack grows, pointers in the stack are updated. · Additionally, each function compares its stack pointer against g->stackguard to check for overflow.
GO STACKS GO FUNCTION PROLOGUE
GO FUNCTION PROLOGUE OR FINDING OPPORTUNITIES TO OVERFLOW THE STACK · Developers call tell go to skip this check to test performance issues with pragma //go:nosplit · Some library functions may call //go:nosplit · Look for opportunities to corrupt the stack · Callers pass arguments in the stack
CONVENTIONS
Placing return values in the stack
ARGUMENTS AND RETURN VALUES
Return values are placed in the stack A opposed to C where return values are placed in registers Arguments are also moved to the stack rather than registers.
Passing arguments in the stack
IT'S ALL ABOUT THE GO LIBRARY FUNCTIONS
USE GO LIBRARIES AS A GUIDE TO UNDERSTAND THE BINARY Understanding go internal libraries can significantly help us understand what is going on the assembly code Read the go docs!
CALLING A GOROUTINE
DEREFENCING RECEIVER
SO WHAT? · We have identified a race condition · Note //go:nosplit for typedmemmove
DEFER Function responsible for concurrency are easy to locate In the case of a defer call: Functions are registered with deferprocStack Check whether a panic has been caught Prepare return values and call deferred functions before exiting function Assure to defer if a panic is caught
GO INTERFACES Interfaces consist of: A pointer to a vtable (which contains function pointers that point to the virtual functions) A value pointer
GO ERROR HANDLING error is an an interface Error handling in clumsy in go Bugs due to unhandled errors are common When checking for error != nil we load the error vtable and error value. Then we test if the value is nil And branch depending on the result
SO WHAT? WELL...
WHAT ELSE SHOULD I LOOK FOR? cgo functions! Hacking like its 1995(98)(2000)
EL FIN
· Go is everywhere · We are likely to see see more go in IoT with projects like TinyGo · Fancy, yet exploitable under the right conditions · Look for programmer errors (i.e. error handling) · Understand stack handling · Look for cgo usage · Leverage go tools · RTFM
· For developers: · Test your code with gosec, govet · Fuzz it with go-fuzz
QUESTIONS?