// note: rva is relative to the module func (m LoadedModule) MemReadPtr(ws *Workspace, rva AS.RVA) (AS.VA, error) { if ws.Mode == MODE_32 { var data uint32 d, e := m.MemRead(ws, rva, 0x4) if e != nil { return 0, e } p := bytes.NewBuffer(d) binary.Read(p, binary.LittleEndian, &data) return AS.VA(uint64(data)), nil } else if ws.Mode == MODE_64 { var data uint64 d, e := m.MemRead(ws, rva, 0x8) if e != nil { return 0, e } p := bytes.NewBuffer(d) binary.Read(p, binary.LittleEndian, &data) return AS.VA(uint64(data)), nil } else { return 0, InvalidModeError } }
func MemReadPointer(as AS.AddressSpace, va AS.VA, mode Mode) (AS.VA, error) { if mode == MODE_32 { var data uint32 d, e := as.MemRead(va, 0x4) if e != nil { return AS.VA(0), e } p := bytes.NewBuffer(d) binary.Read(p, binary.LittleEndian, &data) return AS.VA(uint64(data)), nil } else if mode == MODE_64 { var data uint64 d, e := as.MemRead(va, 0x8) if e != nil { return AS.VA(0), e } p := bytes.NewBuffer(d) binary.Read(p, binary.LittleEndian, &data) return AS.VA(uint64(data)), nil } else { return 0, InvalidModeError } }
// discoverJumpTargets finds the targets of the current instruction that // should be a jump/branch instruction. // returns ErrFailedToResolveTarget if the target is not resolvable. // this should be expected in some cases, like jumping into uninitialized memory. func (ed *EmulatingDisassembler) discoverJumpTargets() ([]AS.VA, error) { jumpVA := ed.emulator.GetInstructionPointer() insn, e := ed.disassembler.ReadInstruction(ed.ws, jumpVA) if e != nil { return nil, e } var jumpTargets []AS.VA if insn.X86.Operands[0].Type == gapstone.X86_OP_IMM { // simple case: // this looks like: jnz 0x401000 jumpTargets = []AS.VA{AS.VA(insn.X86.Operands[0].Imm)} } else if insn.X86.Operands[0].Type == gapstone.X86_OP_MEM { // complex cases: op := insn.X86.Operands[0] if op.Mem.Base == 0 { // this can look like: jmp dword ptr [edx*4 + 0x401000] // segment: 0 // base: 0 // index: 0x18 (X86_REG_EDX) // scale: 4 // disp: 0x401000 jumpTargets, e = ed.findJumpTableTargets(AS.VA(op.Mem.Disp), op.Mem.Scale) check(e) } else { // or this can look like: jmp dword ptr [0x401000] // // or this: jmp dword ptr [ebx + 0x18]" // segment: 0 // base: 0x15 (X86_REG_EBX) // index: 0 // scale: 1 // disp: 0x18 jumpTargets, e = ed.emulateToJumpTargetsAndBack() check(e) } } else if insn.X86.Operands[0].Type == gapstone.X86_OP_REG { // complex case: // this can look like: jmp [edx] jumpTargets, e = ed.emulateToJumpTargetsAndBack() check(e) } else { // this shouldnt really happen. the remaining types are: INVALID, and FP logrus.Debugf("jump target type: %x", insn.X86.Operands[0].Type) jumpTargets, e = ed.emulateToJumpTargetsAndBack() check(e) } if disassembly.IsConditionalJump(insn) { logrus.Debugf("conditional jump") jumpTargets = append(jumpTargets, AS.VA(insn.Address+insn.Size)) } return jumpTargets, nil }
// GetJumpTargets gets the possible addresses to which a known jump instruction // transfers control. // For a conditional jump, get both the true and false targets. // This function uses just the instruction instance, so for an indirect jump, we can't tell much. func GetJumpTargets(insn gapstone.Instruction) ([]*Jump, error) { ret := make([]*Jump, 0, 2) if DoesInstructionHaveGroup(insn, gapstone.X86_GRP_JUMP) && insn.Mnemonic == "jmp" { // unconditional jump, have the following possibilities: // - direct jump: jmp 0x1000 // - indirect jump: jmp eax // - indirect jump: jmp [0x1000]??? next, e := GetJumpTarget(insn) if e != nil { // do the best we can return ret, nil } ret = append( ret, &Jump{ From: AS.VA(insn.Address), To: next, Type: P.JumpTypeUncond, }) } else { // assume a two case situation: // here: // jnz yes // xor eax, eax // ret // yes: // mov eax, 1 // ret falsePc := AS.VA(uint64(insn.Address) + uint64(insn.Size)) ret = append( ret, &Jump{ From: AS.VA(insn.Address), To: falsePc, Type: P.JumpTypeCondFalse, }) truePc, e := GetJumpTarget(insn) if e == nil { ret = append( ret, &Jump{ From: AS.VA(insn.Address), To: truePc, Type: P.JumpTypeCondTrue, }) } } return ret, nil }
/** ControlFlowAnalysis implements FunctionAnalysis interface **/ func (a *ControlFlowAnalysis) AnalyzeFunction(f *artifacts.Function) error { ld, e := LD.New(a.ws) check(e) cj, e := ld.RegisterJumpTraceHandler(func( insn gapstone.Instruction, from_bb AS.VA, target AS.VA, jtype P.JumpType) error { return a.ws.MakeCodeCrossReference(AS.VA(insn.Address), target, jtype) }) check(e) defer ld.UnregisterJumpTraceHandler(cj) cb, e := ld.RegisterBBTraceHandler(func(start AS.VA, end AS.VA) error { return a.ws.MakeBasicBlock(start, end) }) check(e) defer ld.UnregisterBBTraceHandler(cb) c, e := ld.RegisterCallTraceHandler(func(from AS.VA, to AS.VA) error { return a.ws.MakeCallCrossReference(from, to) }) check(e) defer ld.UnregisterCallTraceHandler(c) e = ld.ExploreFunction(a.ws, f.Start) check(e) return nil }
// IterateInstructions invokes the provided callback with each instruction starting // from the given address until the end of the current basic block. func (dis *GapstoneDisassembler) IterateInstructions(as AS.AddressSpace, va AS.VA, f func(insn gapstone.Instruction) (bool, error)) error { for insn, e := dis.ReadInstruction(as, va); e == nil; insn, e = dis.ReadInstruction(as, va) { cont, e := f(insn) if e != nil { return e } if !cont { break } // stop processing a basic block if we're at: RET, IRET, JUMP if DoesInstructionHaveGroup(insn, gapstone.X86_GRP_RET) { break // out of instruction processing loop } else if DoesInstructionHaveGroup(insn, gapstone.X86_GRP_IRET) { break // out of instruction processing loop } else if DoesInstructionHaveGroup(insn, gapstone.X86_GRP_JUMP) { break // out of instruction processing loop } va = AS.VA(uint64(va) + uint64(insn.Size)) } return nil }
func (a *Artifacts) GetBasicBlock(va AS.VA) (*BasicBlock, error) { v, e := a.persistence.GetAddressValueNumber(P.LocationData, va, P.TypeOfLocation) if e != nil { return nil, ErrBasicBlockNotFound } switch v { case int64(P.LocationFunction): // ok case int64(P.LocationBasicBlock): // ok default: return nil, ErrFunctionNotFound } length, e := a.persistence.GetAddressValueNumber(P.BasicBlockData, va, P.BasicBlockLength) if e != nil { return nil, ErrBasicBlockNotFound } return &BasicBlock{ artifacts: a, Start: va, End: AS.VA(uint64(va) + uint64(length)), }, nil }
func New(ws *W.Workspace) (*Emulator, error) { logrus.Debug("emulator: new") if ws.Arch != W.ARCH_X86 { return nil, W.InvalidArchError } if !(ws.Mode == W.MODE_32 || ws.Mode == W.MODE_64) { return nil, W.InvalidModeError } var u uc.Unicorn var e error if ws.Mode == W.MODE_32 { u, e = uc.NewUnicorn(uc.ARCH_X86, uc.MODE_32) } else if ws.Mode == W.MODE_64 { u, e = uc.NewUnicorn(uc.ARCH_X86, uc.MODE_64) } if e != nil { return nil, e } disassembler, e := ws.GetDisassembler() emu := &Emulator{ ws: ws, u: u, disassembler: disassembler, maps: make([]AS.MemoryRegion, 0), } e = AS.CopyAddressSpace(emu, ws) check(e) if e != nil { return nil, e } stackAddress := AS.VA(0x69690000) stackSize := uint64(0x40000) e = emu.MemMap(AS.VA(uint64(stackAddress)-(stackSize/2)), stackSize, "stack") check(e) emu.SetStackPointer(stackAddress) return emu, nil }
func (emu *Emulator) StepInto() error { logrus.Debugf("emulator: step into") var memErr error = nil var codeErr error = nil memHook, e := emu.traceMemUnmapped(&memErr) check(e) defer memHook.Close() // always stop after one instruction hitCount := 0 h, e := emu.HookCode(func(addr AS.VA, size uint32) { if hitCount == 0 { // pass } else if hitCount == 1 { emu.u.Stop() } else { codeErr = EmulatorEscapedError } hitCount++ }) check(e) defer h.Close() insn, e := emu.GetCurrentInstruction() ip := emu.GetInstructionPointer() end := AS.VA(uint64(ip) + uint64(insn.Size)) e = emu.start(ip, end) if e != nil { logrus.Warnf("Single step failed: %s", e.Error()) switch e := e.(type) { case uc.UcError: // TODO: nested switch here // TODO: split out into utility function?? if e == uc.ERR_FETCH_UNMAPPED { return AS.ErrInvalidMemoryExec } else if e == uc.ERR_READ_UNMAPPED { return AS.ErrInvalidMemoryRead } else if e == uc.ERR_WRITE_UNMAPPED { return AS.ErrInvalidMemoryWrite } break default: break } return e } if memErr != nil { return memErr } if codeErr != nil { return codeErr } return nil }
func SkipInstruction(emu *emulator.Emulator, dis disassembly.Disassembler) error { pc := emu.GetInstructionPointer() insn, e := dis.ReadInstruction(emu, pc) check(e) nextPc := AS.VA(insn.Address + insn.Size) logrus.Debugf("Skipping from %s to %s", pc, nextPc) emu.SetInstructionPointer(nextPc) return nil }
/** IndirectControlFlowAnalysis implements FunctionAnalysis interface **/ func (a *IndirectControlFlowAnalysis) AnalyzeFunction(f *artifacts.Function) error { logrus.Debugf("indirect cf analysis: analyze function: %s", f.Start) ed, e := ED.New(a.ws) check(e) defer ed.Close() cj, e := ed.RegisterJumpTraceHandler(func( insn gapstone.Instruction, from_bb AS.VA, target AS.VA, jtype P.JumpType) error { return a.ws.MakeCodeCrossReference(AS.VA(insn.Address), target, jtype) }) check(e) defer ed.UnregisterJumpTraceHandler(cj) cb, e := ed.RegisterBBTraceHandler(func(start AS.VA, end AS.VA) error { return a.ws.MakeBasicBlock(start, end) }) check(e) defer ed.UnregisterBBTraceHandler(cb) c, e := ed.RegisterInstructionTraceHandler(func(insn gapstone.Instruction) error { if disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_CALL) { if insn.X86.Operands[0].Type == gapstone.X86_OP_IMM { // assume we have: call 0x401000 targetva := AS.VA(insn.X86.Operands[0].Imm) a.ws.MakeFunction(targetva) } } return nil }) check(e) defer ed.UnregisterInstructionTraceHandler(c) e = ed.ExploreFunction(a.ws, f.Start) check(e) return nil }
// MemReadPeOffset reads a 32bit (even on x64) AS.VA from the given address // of the module. // note: rva is relative to the module func (m LoadedModule) MemReadPeOffset(ws *Workspace, rva AS.RVA) (AS.VA, error) { // PE header offsets are 32bits even on x64 var data uint32 d, e := m.MemRead(ws, rva, 0x4) if e != nil { return 0, e } p := bytes.NewBuffer(d) binary.Read(p, binary.LittleEndian, &data) return AS.VA(uint64(data)), nil }
// GetJumpTarget gets the address to which a known jump instruction // transfers control. // If the instruction is a conditional jump, then this function returns // the "jump is taken" target. func GetJumpTarget(insn gapstone.Instruction) (AS.VA, error) { // have the following possibilities: // - direct jump: jmp 0x1000 // - indirect jump: jmp eax // - indirect jump: jmp [0x1000]??? if insn.X86.Operands[0].Type == gapstone.X86_OP_IMM { return AS.VA(insn.X86.Operands[0].Imm), nil } else if insn.X86.Operands[0].Type == gapstone.X86_OP_REG { // jump eax // this is indirect, which is unresolvable. // leave analysis to the emulator. return AS.VA(0), ErrFailedToResolveJumpTarget } else if insn.X86.Operands[0].Type == gapstone.X86_OP_MEM { // jump [0x1000] // calling this indirect for now, which is unresolvable. // we could attempt to manually read out the pointer contents // but that should really be left to the emulator. return AS.VA(0), ErrFailedToResolveJumpTarget } return AS.VA(0), nil }
// discoverCallTarget finds the target of the current instruction that // should be a CALL instruction. // returns ErrFailedToResolveTarget if the target is not resolvable. // this should be expected in some cases, like calling into uninitialized memory. // // find call target // - is direct call, like: call 0x401000 // -> directly read target // - is direct call, like: call [0x401000] ; via IAT // -> read IAT, use MSDN doc to determine number of args? // - is indirect call, like: call EAX // -> just save PC, step into, read PC, restore PC, pop SP // but be sure to handle invalid fetch errors func (ed *EmulatingDisassembler) discoverCallTarget() (AS.VA, error) { var callTarget AS.VA callVA := ed.emulator.GetInstructionPointer() insn, e := ed.disassembler.ReadInstruction(ed.ws, callVA) if e != nil { return 0, e } if insn.X86.Operands[0].Type == gapstone.X86_OP_MEM { // assume we have: call [0x4010000] ; IAT iva := AS.VA(insn.X86.Operands[0].Mem.Disp) if e == nil { // we successfully resolved an imported function. // TODO: how are we marking xrefs to imports? i guess with xrefs to the IAT callTarget = iva } else { // this is not an imported function, so we'll just have to try and see. // either there's a valid function pointer at the address, or we'll get an invalid fetch. callTarget, e = ed.emulateToCallTargetAndBack() if e != nil { logrus.Debugf("EmulateBB: emulating: failed to resolve call: %s", callVA) return 0, ErrFailedToResolveTarget } } } else if insn.X86.Operands[0].Type == gapstone.X86_OP_IMM { // assume we have: call 0x401000 callTarget = AS.VA(insn.X86.Operands[0].Imm) } else if insn.X86.Operands[0].Type == gapstone.X86_OP_REG { // assume we have: call eax callTarget, e = ed.emulateToCallTargetAndBack() if e != nil { logrus.Debugf("EmulateBB: emulating: failed to resolve call: %s", callVA) return 0, ErrFailedToResolveTarget } } return callTarget, nil }
func (emu *Emulator) StepOver() error { logrus.Debugf("emulator: step over") insn, e := emu.GetCurrentInstruction() check(e) if e != nil { return e } if dis.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_CALL) { return emu.RunTo(AS.VA(uint64(emu.GetInstructionPointer()) + uint64(insn.Size))) } else { return emu.StepInto() } }
// ExploreBB linearly disassembles instructions starting at a given address // in a given address space, invoking the appropriate callbacks, and terminates // at the end of the current basic block. // A basic block is delimited by a ret or jump instruction. // Returns the addresses to which this basic block may transfer control via jumps. func (ld *LinearDisassembler) ExploreBB(as AS.AddressSpace, va AS.VA) ([]AS.VA, error) { logrus.Debugf("linear disassembler: explore bb: %s", va) bbStart := va // the last VA reached while exploring tihs BB // only makes sense to fetch this value after iterating instructions lastVa := AS.VA(0) nextBBs := make([]AS.VA, 0, 2) e := ld.disassembler.IterateInstructions(as, va, func(insn gapstone.Instruction) (bool, error) { lastVa = AS.VA(insn.Address) check(ld.EmitInstruction(insn)) if disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_JUMP) { // this return a slice with zero length, but that should be ok targets, e := disassembly.GetJumpTargets(insn) if e != nil { return false, e } for _, target := range targets { check(ld.EmitJump(insn, bbStart, target.To, target.Type)) nextBBs = append(nextBBs, target.To) } // though we can assume that IterateInstructions will return after this insn (end of bb), // we'd better not make assumptions. here, we explicitly end processing. return false, nil // continue processing instructions } else if disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_CALL) { if insn.X86.Operands[0].Type == gapstone.X86_OP_IMM { // example: call 0x401000 targetva := AS.VA(insn.X86.Operands[0].Imm) check(ld.EmitCall(AS.VA(insn.Address), targetva)) } else if insn.X86.Operands[0].Type == gapstone.X86_OP_REG { // example: call eax // indirect call, we're stuck } else { op := insn.X86.Operands[0] if op.Mem.Base != 0 || op.Mem.Index != 0 { // example: call [eax] or call [eax + 10] // indirect call, we're stuck } else { // example: call [GetVersionA] targetva := AS.VA(op.Mem.Disp) check(ld.EmitCall(AS.VA(insn.Address), targetva)) } } } return true, nil // continue processing instructions }) check(e) check(ld.EmitBB(bbStart, lastVa)) return nextBBs, nil }
func (ed *EmulatingDisassembler) findJumpTableTargets(disp AS.VA, scale int) ([]AS.VA, error) { var targets []AS.VA va := disp for { logrus.Debugf("pointer read: %s", va) target, e := W.MemReadPointer(ed.emulator, va, ed.emulator.GetMode()) check(e) if ProbeMemory(ed.emulator, target, uint64(scale)) { targets = append(targets, target) // not sure this conversion from negative number to uint64 is correct va = AS.VA(uint64(va) + uint64(scale)) } else { break } } return targets, nil }
func HookSnapshot(emu *Emulator, snap *Snapshot) error { logrus.Debugf("snapshot: hook") if snap.hook != nil { return ErrSnapshotHookAlreadyActive } h, e := emu.HookMemWrite(func(access int, addr AS.VA, size int, value int64) { for i := uint64(addr); i < uint64(addr)+uint64(size); i += AS.PAGE_SIZE { snap.memory.MarkDirty(AS.VA(i)) } }) check(e) if e != nil { return e } snap.hook = h return nil }
func (emu *Emulator) GetInstructionPointer() AS.VA { logrus.Debug("emulator: get insn pointer") var r uint64 var e error if emu.ws.Arch == W.ARCH_X86 { if emu.ws.Mode == W.MODE_32 { r, e = emu.RegRead(uc.X86_REG_EIP) } else if emu.ws.Mode == W.MODE_64 { r, e = emu.RegRead(uc.X86_REG_RIP) } else { panic(W.InvalidModeError) } } else { panic(W.InvalidArchError) } if e != nil { panic(e) } return AS.VA(r) }
func (loader *PELoader) Load(ws *workspace.Workspace) (*workspace.LoadedModule, error) { var imageBase AS.VA var addressOfEntryPoint AS.RVA var dataDirectory [16]pe.DataDirectory if optionalHeader, ok := loader.file.OptionalHeader.(*pe.OptionalHeader32); ok { imageBase = AS.VA(optionalHeader.ImageBase) addressOfEntryPoint = AS.RVA(optionalHeader.AddressOfEntryPoint) dataDirectory = optionalHeader.DataDirectory } else { return nil, workspace.InvalidModeError } mod := &workspace.LoadedModule{ Name: loader.name, BaseAddress: imageBase, EntryPoint: addressOfEntryPoint.VA(imageBase), Imports: map[AS.RVA]workspace.LinkedSymbol{}, ExportsByName: map[string]workspace.ExportedSymbol{}, ExportsByOrdinal: map[uint16]workspace.ExportedSymbol{}, } for _, section := range loader.file.Sections { e := loader.loadPESection(ws, mod, section) check(e) } e := loader.resolveImports(ws, mod, dataDirectory) check(e) e = loader.resolveExports(ws, mod, dataDirectory) check(e) e = ws.AddLoadedModule(mod) check(e) return mod, nil }
func main() { app := cli.NewApp() app.Version = "0.1" app.Name = "run_linear_disassembler" app.Usage = "Invoke linear disassembler." app.Flags = []cli.Flag{inputFlag, fvaFlag} app.Action = func(c *cli.Context) { if utils.CheckRequiredArgs(c, []cli.StringFlag{inputFlag, fvaFlag}) != nil { return } inputFile := c.String("input_file") if !utils.DoesPathExist(inputFile) { log.Printf("Error: file %s must exist", inputFile) return } fva, e := strconv.ParseUint(c.String("fva"), 0x10, 64) check(e) check(doit(inputFile, AS.VA(fva))) } app.Run(os.Args) }
func doit(path string, fva AS.VA) error { runtime.LockOSThread() logrus.SetLevel(logrus.DebugLevel) exe, e := pe.Open(path) check(e) persis, e := config.MakeDefaultPersistence() check(e) ws, e := W.New(W.ARCH_X86, W.MODE_32, persis) check(e) dis, e := ws.GetDisassembler() check(e) loader, e := peloader.New(path, exe) check(e) _, e = loader.Load(ws) check(e) check(config.RegisterDefaultAnalyzers(ws)) check(ws.MakeFunction(fva)) f, e := ws.Artifacts.GetFunction(fva) check(e) fmt.Printf("digraph asm {\n") fmt.Printf(" node [shape=plain, style=\"rounded\", fontname=\"courier\"]\n") var exploreBBs func(bb *artifacts.BasicBlock) error exploreBBs = func(bb *artifacts.BasicBlock) error { fmt.Printf("bb_%s [label=<\n", bb.Start) fmt.Printf("<TABLE BORDER='1' CELLBORDER='0'>\n") insns, e := bb.GetInstructions(dis, ws) check(e) for _, insn := range insns { d, e := ws.MemRead(AS.VA(insn.Address), uint64(insn.Size)) check(e) // format each of those as hex var bytesPrefix []string for _, b := range d { bytesPrefix = append(bytesPrefix, fmt.Sprintf("%02X", b)) } prefix := strings.Join(bytesPrefix, " ") fmt.Printf(" <TR>\n") fmt.Printf(" <TD ALIGN=\"LEFT\">\n") fmt.Printf(" %s\n", AS.VA(insn.Address)) fmt.Printf(" </TD>\n") fmt.Printf(" <TD ALIGN=\"LEFT\">\n") fmt.Printf(" %s\n", prefix) fmt.Printf(" </TD>\n") fmt.Printf(" <TD ALIGN=\"LEFT\">\n") fmt.Printf(" %s\n", insn.Mnemonic) fmt.Printf(" </TD>\n") fmt.Printf(" <TD ALIGN=\"LEFT\">\n") fmt.Printf(" %s\n", insn.OpStr) fmt.Printf(" </TD>\n") fmt.Printf(" </TR>\n") } fmt.Printf("</TABLE>\n") fmt.Printf(">];\n") nextBBs, e := bb.GetNextBasicBlocks() check(e) for _, nextBB := range nextBBs { exploreBBs(nextBB) } for _, nextBB := range nextBBs { fmt.Printf("bb_%s -> bb_%s;\n", bb.Start, nextBB.Start) } return nil } firstBB, e := f.GetFirstBasicBlock() check(e) exploreBBs(firstBB) defer fmt.Printf("}") runtime.UnlockOSThread() return nil }
func (m *hookMultiplexer) Install(emu *Emulator, hookType int) error { // TODO: ensure multiplexer not already installed if m.h != nil { return ErrAlreadyHooked } switch hookType { case uc.HOOK_MEM_READ: h, e := emu.u.HookAdd( uc.HOOK_MEM_READ, func(mu uc.Unicorn, access int, addr uint64, size int, value int64) { for _, f := range m.entries { if f, ok := f.(MemReadHandler); ok { f(access, AS.VA(addr), size, value) } else { log.Printf("error: failed to convert handler to mem read handler") } } }) check(e) if e != nil { return e } logrus.Debugf("hooks: added hook: %v", h) m.h = &UnicornCloseableHook{emu: emu, h: h} return nil case uc.HOOK_MEM_WRITE: h, e := emu.u.HookAdd( uc.HOOK_MEM_WRITE, func(mu uc.Unicorn, access int, addr uint64, size int, value int64) { for _, f := range m.entries { if f, ok := f.(MemWriteHandler); ok { f(access, AS.VA(addr), size, value) } else { log.Printf("error: failed to convert handler to mem write handler") } } }) check(e) if e != nil { return e } logrus.Debugf("hooks: added hook: %v", h) m.h = &UnicornCloseableHook{emu: emu, h: h} return nil case uc.HOOK_MEM_UNMAPPED: h, e := emu.u.HookAdd( uc.HOOK_MEM_UNMAPPED, func(mu uc.Unicorn, access int, addr uint64, size int, value int64) bool { for _, f := range m.entries { if f, ok := f.(MemUnmappedHandler); ok { if !f(access, AS.VA(addr), size, value) { return false } } else { log.Printf("error: failed to convert handler to mem unmapped handler") } } return true }) check(e) if e != nil { return e } logrus.Debugf("hooks: added hook: %v", h) m.h = &UnicornCloseableHook{emu: emu, h: h} return nil case uc.HOOK_CODE: h, e := emu.u.HookAdd( uc.HOOK_CODE, func(mu uc.Unicorn, addr uint64, size uint32) { for _, f := range m.entries { if f, ok := f.(CodeHandler); ok { f(AS.VA(addr), uint32(size)) } else { log.Printf("error: failed to convert handler to mem unmapped handler") } } }) check(e) if e != nil { return e } logrus.Debugf("hooks: added hook: %v", h) m.h = &UnicornCloseableHook{emu: emu, h: h} return nil default: return ErrInvalidArgument } }
// when/where can this function be safely called? func (ed *EmulatingDisassembler) ExploreBB(as AS.AddressSpace, va AS.VA) ([]AS.VA, error) { logrus.Debugf("emu disassembler: explore bb: %s", va) // things done here: // - find CALL instructions // - emulate to CALL instructions // - using emulation, figure out what the target of the call is // - using linear disassembly, find target calling convention // - decide how much stack to clean up // - manually move PC to instruction after the CALL // - clean up stack // - continue emulating // - resolve jump targets at end of BB using emulation var nextBBs []AS.VA bbStart := va var callVAs []AS.VA // recon endVA := va e := ed.disassembler.IterateInstructions(as, va, func(insn gapstone.Instruction) (bool, error) { logrus.Debugf("recon: insn: %s", AS.VA(insn.Address)) endVA = AS.VA(insn.Address) // update last reached VA, to compute end of BB if !disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_CALL) { return true, nil } logrus.Debugf("EmulateBB: planning: found call: va: 0x%x", insn.Address) callVAs = append(callVAs, AS.VA(insn.Address)) return true, nil // continue processing instructions }) check(e) // prepare emulator ed.emulator.SetInstructionPointer(va) // emulate! for len(callVAs) > 0 { callVA := callVAs[0] callVAs = callVAs[1:] logrus.Debugf("EmulateBB: emulating: from: %s to: %s", ed.emulator.GetInstructionPointer(), callVA) e := ed.bulletproofRun(callVA) check(e) pc := ed.emulator.GetInstructionPointer() insn, e := ed.disassembler.ReadInstruction(ed.ws, pc) check(e) if !disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_CALL) { panic(fmt.Sprintf("expected to be at a call, but we're not: %s", pc)) } logrus.Debugf("EmulateBB: paused at call: %s", pc) // this instruction may be emitted twice, since we potentially // use emulation to resolve the call target check(ed.EmitInstruction(insn)) var stackDelta int64 callTarget, e := ed.discoverCallTarget() if e == ErrFailedToResolveTarget { // will just have to make a guess as to how to clean up the stack // for right now, assume its 0 // TODO: if its an import, assume STDCALL // and use MSDN documentation to extract number of parameters } else if e != nil { e := ed.ws.MakeFunction(callTarget) check(e) f, e := ed.ws.Artifacts.GetFunction(callTarget) check(e) stackDelta, e = f.GetStackDelta() check(e) } check(ed.EmitCall(pc, callTarget)) logrus.Debugf("EmulateBB: skipping call: %s", pc) check(SkipInstruction(ed.emulator, ed.disassembler)) // cleanup stack ed.emulator.SetStackPointer(AS.VA(int64(ed.emulator.GetStackPointer()) + stackDelta)) } // emulate to end of current basic block logrus.Debugf("EmulateBB: emulating to end: from: %s to: %s", ed.emulator.GetInstructionPointer(), endVA) e = ed.bulletproofRun(endVA) check(e) pc := ed.emulator.GetInstructionPointer() check(ed.EmitBB(bbStart, pc)) insn, e := ed.disassembler.ReadInstruction(ed.ws, pc) check(e) logrus.Debugf("EmulateBB: final instruction: %s", pc) check(ed.EmitInstruction(insn)) if disassembly.DoesInstructionHaveGroup(insn, gapstone.X86_GRP_RET) { logrus.Debugf("EmulateBB: ends with a ret") } else { // must be a jump nextBBs, e = ed.discoverJumpTargets() check(e) logrus.Debugf("EmulateBB: next BBs: %v", nextBBs) for _, target := range nextBBs { // TODO: use real jump types check(ed.EmitJump(insn, bbStart, target, P.JumpTypeUncond)) } } return nextBBs, nil }