Skip to content

Instantly share code, notes, and snippets.

@bnagy
Last active August 29, 2015 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bnagy/10615741 to your computer and use it in GitHub Desktop.
Save bnagy/10615741 to your computer and use it in GitHub Desktop.
gootool
package main
import (
"bytes"
"container/list"
"debug/macho"
"encoding/hex"
"flag"
"fmt"
cs "github.com/bnagy/gapstone"
"log"
"os"
"path"
)
// https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html#//apple_ref/doc/uid/20001298-BAJFFCGF
// N_SECT (0xe)—The symbol is defined in the section number given in n_sect.
// ( if this bit is set in the type byte, it means the n_value will be an address )
const N_SECT = uint8(0x0e)
const REFERENCED_DYNAMICALLY = uint16(0x0010)
type SymList struct {
*list.List
db map[uint]macho.Symbol
}
// Make a ghetto symbol "DB" and fill the linked list
// Map is for O(1) address->string lookups, list is for sym+offset lookups
func (sl *SymList) Add(sym macho.Symbol) {
sl.db[uint(sym.Value)] = sym
for s := sl.Back(); s != nil; s = s.Prev() {
this := s.Value.(macho.Symbol)
if sym.Value > this.Value {
sl.InsertAfter(sym, s)
return
}
}
// Wasn't inserted after anything, must be lowest value
sl.PushFront(sym)
}
func (sl *SymList) Near(addr uint64) (sym macho.Symbol, offset int, found bool) {
for s := sl.Back(); s != nil; s = s.Prev() {
this := s.Value.(macho.Symbol)
if addr >= this.Value {
return this, int(addr - this.Value), true
}
}
return macho.Symbol{}, 0, false
}
func (sl *SymList) At(addr uint) (sym macho.Symbol, found bool) {
sym, ok := sl.db[addr]
return sym, ok
}
func NewSymList() *SymList {
return &SymList{
list.New(),
make(map[uint]macho.Symbol),
}
}
func inGroup(insn cs.Instruction, grp uint) bool {
for _, g := range insn.Groups {
if g == grp {
return true
}
}
return false
}
func dumpResolvedImmediate(buf *bytes.Buffer, insn cs.Instruction, sym macho.Symbol, off int) {
if off > 0 {
fmt.Fprintf(
buf,
"0x%x: %-24.24s %-12.12s%s+0x%x [ %s ]\n",
insn.Address,
hex.EncodeToString(insn.Bytes),
insn.Mnemonic,
sym.Name,
off,
insn.OpStr,
)
} else {
fmt.Fprintf(
buf,
"0x%x: %-24.24s %-12.12s%s [ %s ]\n",
insn.Address,
hex.EncodeToString(insn.Bytes),
insn.Mnemonic,
sym.Name,
insn.OpStr,
)
}
}
func dumpUnresolvedImmediate(buf *bytes.Buffer, insn cs.Instruction) {
fmt.Fprintf(
buf,
"0x%x: %-24.24s %-12.12s%s [ ??? ]\n",
insn.Address,
hex.EncodeToString(insn.Bytes),
insn.Mnemonic,
insn.OpStr,
)
}
func dumpInsn(buf *bytes.Buffer, insn cs.Instruction) {
fmt.Fprintf(
buf,
"0x%x: %-24.24s %-12.12s%s\n",
insn.Address,
hex.EncodeToString(insn.Bytes),
insn.Mnemonic,
insn.OpStr,
)
}
func main() {
flag.Parse()
machOObj, err := macho.Open(flag.Arg(0))
if err != nil {
fmt.Fprintf(
os.Stderr,
"Unable to open Mach-O binary \"%v\": %v\n"+
"Usage: %s [filename]\n",
flag.Arg(0),
err,
path.Base(os.Args[0]),
)
os.Exit(1)
}
textSection := machOObj.Section("__text")
if textSection == nil {
log.Fatal("Text section not found.")
}
textBytes, err := textSection.Data()
if err != nil {
log.Fatalf("Error parsing __text: %v", err)
}
symList := NewSymList()
for _, sym := range machOObj.Symtab.Syms {
// TODO: MACH-O SYMBOLS, HOW DO THEY WORK?
if sym.Sect == 1 && // text section
sym.Type&N_SECT > 0 && // N_SECT ( internal or external )
sym.Name != "" && // Don't know what these blank names are :/
sym.Desc != REFERENCED_DYNAMICALLY {
symList.Add(sym)
}
}
engine, err := cs.New(
cs.CS_ARCH_X86,
cs.CS_MODE_64,
)
if err == nil {
defer engine.Close()
engine.SetOption(cs.CS_OPT_DETAIL, cs.CS_OPT_ON)
base := symList.Front().Value.(macho.Symbol).Value
cursor := uint64(0)
buf := new(bytes.Buffer)
disasm:
for {
if cursor >= uint64(len(textBytes)) {
break disasm
}
insns, err := engine.Disasm(
textBytes[cursor:], // code buffer
cursor+base, // starting address
0, // insns to disassemble, 0 for all
)
if err != nil {
log.Fatalf("Disassembly error: %v", err)
}
for _, insn := range insns {
cursor = uint64(insn.Address) - base
buf.Reset()
// Mark up symbols as ( hopefully ) function heads
if _, ok := symList.At(insn.Address); ok {
// The Lookup names are usually nicer - eg you get
// main.validateSignature instead of _text
s, _, _ := symList.Near(uint64(insn.Address))
fmt.Printf("\n%v:\n", s.Name)
}
// Try to symbolically resolve any jmp/call with an immediate operand
if (inGroup(insn, cs.X86_GRP_JUMP) || insn.Id == cs.X86_INS_CALL) &&
insn.X86.Operands[0].Type == cs.X86_OP_IMM {
imm := uint64(insn.X86.Operands[0].Imm)
if sym, off, found := symList.Near(imm); found {
dumpResolvedImmediate(buf, insn, sym, off)
} else {
dumpUnresolvedImmediate(buf, insn)
}
fmt.Print(buf.String())
continue //insn loop
}
// fallthrough
dumpInsn(buf, insn)
fmt.Print(buf.String())
} // end insn loop
// If there's a symbol > the end cursor, start disassembling again
// from that symbol, in case we have:
// 0x2000 __text: CODE
// 0x2ff8 GARBAGE ( capstone disassembly will error )
// 0x3000 some_new_sym: MORE CODE
for s := symList.Front(); s != nil; s = s.Next() {
this := s.Value.(macho.Symbol)
if this.Value > cursor+base {
cursor = this.Value - base
continue disasm
}
}
break
} // end disasm loop
return
}
log.Fatalf("Failed to open engine: %v", err)
}
$ go run gootool.go /usr/local/bin/gpg | head -40
start:
0x26c0: 6a00 push 0
0x26c2: 89e5 mov ebp, esp
0x26c4: 83e4f0 and esp, -0x10
0x26c7: 83ec10 sub esp, 0x10
0x26ca: 8b5d04 mov ebx, dword ptr [rbp + 4]
0x26cd: 895c2400 mov dword ptr [rsp], ebx
0x26d1: 8d4d08 lea ecx, dword ptr [rbp + 8]
0x26d4: 894c2404 mov dword ptr [rsp + 4], ecx
0x26d8: 83c301 add ebx, 1
0x26db: c1e302 shl ebx, 2
0x26de: 01cb add ebx, ecx
0x26e0: 895c2408 mov dword ptr [rsp + 8], ebx
0x26e4: 8b03 mov eax, dword ptr [rbx]
0x26e6: 83c304 add ebx, 4
0x26e9: 85c0 test eax, eax
0x26eb: 75f7 jne start+0x24 [ 0x26e4 ]
0x26ed: 895c240c mov dword ptr [rsp + 0xc], ebx
0x26f1: e81a270000 call _main [ 0x4e10 ]
0x26f6: 89442400 mov dword ptr [rsp], eax
0x26fa: e8a1820b00 call _gnupg_rl_initialize+0x174e7 [ 0xba9a0 ]
0x26ff: f4 hlt
dyld_stub_binding_helper:
0x2700: e800000000 call dyld_stub_binding_helper+0x5 [ 0x2705 ]
0x2705: 58 pop rax
0x2706: ffb08ba10b00 push qword ptr [rax + 0xba18b]
0x270c: 8b80fb980b00 mov eax, dword ptr [rax + 0xb98fb]
0x2712: ffe0 jmp rax
__dyld_func_lookup:
0x2714: e800000000 call __dyld_func_lookup+0x5 [ 0x2719 ]
0x2719: 58 pop rax
0x271a: 8b80eb980b00 mov eax, dword ptr [rax + 0xb98eb]
0x2720: ffe0 jmp rax
0x2722: 90 nop
0x2723: 90 nop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment