Last active
August 29, 2015 13:59
-
-
Save bnagy/10615741 to your computer and use it in GitHub Desktop.
gootool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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