Blog

Go Hacking - Part 2

May 1, 2019 | 3 minutes read
Share this:

Tags: go, reversing

In Go Hacking - Part 1 we saw how to quickly assemble some code to find and possibly exploit code caves in Windows binaries. Moving forward we will now build a small disassembler. It’s just a proof of concept, however the code shown here can be quickly expanded to other contexts and platforms since Go is famous for natively supporting many (and sometimes exotic) architectures.

Building a Mini Disassembler

Let’s begin by opening a windows binary and dumping its executable code (located in .text section). The way of doing that is quite straightforward as previously seen:

package main

import (
	"debug/pe"
	"fmt"
	"log"
	"os"
)

func main() {
	f, err := pe.Open(os.Args[1])
	if err != nil {
		log.Fatalln(err)
	}
	defer f.Close()

	code, _ := f.Section(".text").Data()
	fmt.Printf("% x\n", code)
}

For the purpose of this article, let’s try the code above with the same calc.exe 64-bit binary. The output is, not surprisingly, a long string of bytes (shortened here for brevity):

$ ./disam calc.exe 
cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 40 53 48 83 ec 40 4c 8b 94 24 80
00 00 00 48 8b d9 4d 85 d2 74 5c 4c 8b 5c 24 70 8b c2 85 d2 74 23 83 f8 01 75 22
41 0f b6 c8 b8 00 01 00 00 ff c1 4d 89 4a 10 45 84 c0 4d 89 5a 18 0f 45 c1 41 89
02 eb 04 41 83 22 00 49 8b 42 28 48 85 c0 74 21 49 8b 4a 30 48 89 4c 24 30 48 8b
4c 24 78 48 89 4c 24 28 48 8b cb [...]

We can disassemble those bytes using little more than 5 lines of code. Don’t believe me? Then let’s have a look at our Disasm() function:

// Disasm disassembles either 32 or 64-bit intel instructions.
func Disasm(data []byte, mode uint64) {
	p := 0
	for p < len(data) {
		op, _ := x86asm.Decode(data[p:], int(mode))
		x86asm.IntelSyntax(op, mode, nil)
		fmt.Println(x86asm.IntelSyntax(op, mode, nil))
		p += op.Len
	}
}

First we loop through the bytes to be disassembled, decode then as either 32 or 64 bit instructions (mode variable) and finally print the result using the Intel syntax. The complete code, including error checking and other basic functionalities, can be found on my Github account. I urge you to try it for yourself. Below is an excerpt of the disassembled code produced by our little program:

$ ./disam calc.exe 
0x140001010: push rbx
0x140001012: sub rsp, 0x40
0x140001016: mov r10, qword ptr [rsp+0x80]
0x14000101e: mov rbx, rcx
0x140001021: test r10, r10
0x140001024: jz 0x9e
0x140001026: mov r11, qword ptr [rsp+0x70]
0x14000102b: mov eax, edx
0x14000102d: test edx, edx
0x14000102f: jz 0x65
[...]

Compare it to the same disassembly generated by Binary Ninja:

Disassembly

We are still missing a way to deference the addresses and surely our Linear Sweep disassembly algorithm needs improvement. We could keeping adding more features to our program but I hope I have already piqued your curiosity.

Final Thoughts

I find it important that Cybersec professionals or enthusiasts like myself feel comfortable with programming and indeed most do. Nonetheless, going beyond the usual Python or Powershell script can make a difference and I think that Go nicely fits this purpose: it has some of that simplicity that only a scripting language offers while providing an easy way to compile binaries that are fast and self contained (i.e. without external dependencies). On top of that the Go ecosystem already benefits from a large set of libraries that make networking, reverse engineering and integration with the OS a breeze.

comments powered by Disqus