Go For Hacking : Endianness Conversion

Summary

Endianness conversion, a very common task used in exploit development to represent address spaces, can be done in Go using the bytes and encoding/binary packages. It can replace the struct.pack method from Python 2. Example and explaination below.

Introduction

Here is a quick post that possibly could be part of serie as I keep exploring Go applied to offensive computer security broadly speaking. Despite some great tools written in Go, and a few public exploits written in it, there is still surprisingly few resources focused on how to use Go for things like pentesting, exploit writing and hacking in general. There is the really good book Black Hat Go published on No Starch Press, but beside that, I still struggle to find written resources online for Go regarding hacking and security.

Endianness Conversion : Python 2 VS Go

I won’t try to cover the many possible reasons as to why things are like they are. Python 2 is still one of the main programming language in hacking, Go adoption is not as widespread, maybe people able to write low-level kernel exploits are more than fine sticking with plain old C, etc.

I would rather like to cover a very specific point I struggled with when beginning with exploit development : Endianness and how to convert an hexadecimal address into little endian. Achieving this simple task in a Go program took me quite some time.

In Python 2, converting the endianness of a hex value can easily be done using the struct module. test in this context is a memory address.

import struct

test = -0x21524111
print struct.pack("<i", test)

Using xxd, we can check the results of this conversion in hexadecimal format :

python2 SRC/python/learning/little_endian.py | xxd
00000000: efbe adde 0a                             .....

In Go, things are a little bit more verbose. One of the main difference in this context is that Go strings are immutable sequences of UTF-8 encoded bytes (slices), while Python 2 strings are just raw bytes. This means additional conversion and formatting will need to be performed to display the same output using a given ASCII representation of an hex address. It can be done by creating a new bytes buffer using the bytes package and converting to the right endianness with encoding/binary.

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

func main() {
	buf1 := new(bytes.Buffer)
	err := binary.Write(buf1, binary.LittleEndian, int32(-0x21524111))
	if err != nil {
		fmt.Println("binary.Write failed:", err)
	}
	fmt.Printf("%# s\n", buf1)
}

A couple of things to note about this piece of code :

  • Data processed by binary.Write() must be fixed-size data, hence the int32 type-casting on the value instead of passing it “naked”.
  • The Printf formatting flags can be explained as follow (straight from the documentation) :
    • # : alternate format, add leading 0b for binary
    • <space> : put spaces between bytes printing strings or slices in hex
    • s : the uninterpreted bytes of the string or slice
go run SRC/go/examples/little_endian_conversion.go | xxd
00000000: efbe adde 0a                             .....

It’s a bit more work for a simple task, but it’s a small price to pay for what Go has to offer in term of robustness and coding confort.