Exploiting a Go Binary


Earlier this year, tylerni7 showed us a proof of concept for a 32 bit Go exploit using this issue. geohot and I had a wager over who could get the first remote code execution on play.golang.org: he won, but just barely ;-). Props also to ricky for helping to find the underlying cause/writing the patch. Here is a summary of how we did it.

Note: play.golang.org is properly sandboxed, so code execution there does not actually let you do anything. Had this been a more serious bug that could actually be used for anything malicious, we would have reported it and not used it as a CTF problem.

This post is cross posted on my personal blog, original post there.

The Bug

Go has support for embedded structs. You can define an embedded struct as follows:

It is valid to do both instance.bar and instance.foo.

The problem comes when you try something slightly trickier:

When you access instance.foo (a member of an uninitialized struct), it incorrectly offsets from 0 rather than the base of an Embedded struct. Normally, when dereferencing a pointer inside a struct, the go compiler emits guard code which will cause a segfault if the pointer is nil. However, this code is not emitted when the pointer is the first element of the struct, since it’s assumed that this will cause a segfault whenever it is used anyway. This assumption is not always valid, as the pointer can be to a large struct such that the offsets of members of the large struct are valid addresses.

The Vulnerability

We define an enormous struct and use it to offset memory:

Now we can do instance.address = 0xdeadbeef and we have written to 0x400100! This is the arbitrary write primitive we need.

The Exploit

Once you have an arbitrary write in go, it is really easy to get arbitrary code execution. We put a function pointer in our data segment (we wanted to put it in the heap, but that didn’t work on 64bit Go – apparently the size of a struct is limited to 32 bits. Luckily, the data segment is in the lower 32 bits) and change it to point to our shell code using the arbitrary write. Since Go has no randomization at all, this is as simple as running the program twice. Full exploit below:

What Now?

Well, clearly this issue should be fixed. I also think it is important for Go to add the standard protections (ASLR, NX) – I posted an article earlier about security in Go where I strongly advocated those protections. If this language is to be taken seriously, it should really start worrying about making exploitation difficult.

Edit: this article was written a while ago. The the above exploit will not work because Go 1.1 uses a non-executable heap and stack (the vulnerability still gives an arbitrary read/write, but a little extra work is needed to complete the exploit). Good job go!

Writeup by Alex Reece, see me on Google+ or my blog. geohot can be found on his website.

  • u

    Nice finding :)

    Remember playing your CTF last year. Was fun even though the vulnerability was through the use of cgo.

    Congratulations on finding a pure Go vulnerability!

    Cheers /u