Created
May 19, 2015 23:54
-
-
Save mrexodia/892ff9958d6cae36d867 to your computer and use it in GitHub Desktop.
python script for call stack
This file contains 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
""" | |
A script that tries to determine the call stack | |
Run the application with the debugger, suspend the debugger, select a thread and finally run the script. | |
Copyright (c) 1990-2009 Hex-Rays | |
ALL RIGHTS RESERVED. | |
v1.0 - initial version | |
v1.0.1 - added stack segment bitness detection, thus works with 64bit processes too | |
""" | |
import idaapi | |
import idc | |
import idautils | |
# ----------------------------------------------------------------------- | |
# class to take a copy of a segment_t | |
class Seg(): | |
def __init__(self, s): | |
self.startEA = s.startEA | |
self.endEA = s.endEA | |
self.perm = s.perm | |
self.bitness = s.bitness | |
def __cmp__(self, other): | |
return cmp(self.startEA, other.startEA) | |
# ----------------------------------------------------------------------- | |
# each item described as: | |
# [ delta, [ opcode(s) ] ] | |
#FF10 call d,[eax] | |
#FF5000 call d,[eax][0] | |
#FF9044332211 call d,[eax][011223344] | |
#FF1500000100 call d,[000010000] | |
#FF9300000000 call d,[ebx][0] | |
#FF10 call d,[eax] | |
CallPattern = \ | |
[ | |
[-2, [0xFF] ], | |
[-3, [0xFF] ], | |
[-5, [0xE8] ], | |
[-6, [0xFF] ], | |
] | |
# ----------------------------------------------------------------------- | |
def IsPrevInsnCall(ea): | |
""" | |
Given a return address, this function tries to check if previous instruction | |
is a CALL instruction | |
""" | |
global CallPattern | |
if ea == idaapi.BADADDR or ea < 10: | |
return None | |
for delta, opcodes in CallPattern: | |
# assume caller's ea | |
caller = ea + delta | |
# get the bytes | |
bytes = [x for x in GetDataList(caller, len(opcodes), 1)] | |
# do we have a match? is it a call instruction? | |
if bytes == opcodes and idaapi.is_call_insn(caller): | |
return caller | |
return None | |
# ----------------------------------------------------------------------- | |
def CallStackWalk(nn): | |
class Result: | |
""" | |
Class holding the result of one call stack item | |
Each call stack item instance has the following attributes: | |
caller = ea of caller | |
displ = display string | |
sp = stack pointer | |
""" | |
def __init__(self, caller, sp): | |
self.caller = caller | |
self.sp = sp | |
f = idaapi.get_func(caller) | |
self.displ = "%08x: " % caller | |
if f: | |
self.displ += idc.GetFunctionName(caller) | |
t = caller - f.startEA | |
if t > 0: self.displ += "+" + hex(t) | |
else: | |
self.displ += hex(caller) | |
self.displ += " [" + hex(sp) + "]" | |
def __str__(self): | |
return self.displ | |
# get stack pointer | |
sp = cpu.Esp | |
seg = idaapi.getseg(sp) | |
if not seg: | |
return (False, "Could not locate stack segment!") | |
stack_seg = Seg(seg) | |
word_size = 2 ** (seg.bitness + 1) | |
callers = [] | |
sp = cpu.Esp - word_size | |
while sp < stack_seg.endEA: | |
sp += word_size | |
ptr = idautils.GetDataList(sp, 1, word_size).next() | |
seg = idaapi.getseg(ptr) | |
# only accept executable segments | |
if (not seg) or ((seg.perm & idaapi.SEGPERM_EXEC) == 0): | |
continue | |
# try to find caller | |
caller = IsPrevInsnCall(ptr) | |
# we have no recognized caller, skip! | |
if caller is None: | |
continue | |
# do we have a debug name that is near? | |
if nn: | |
ret = nn.find(caller) | |
if ret: | |
ea = ret[0] | |
# function exists? | |
f = idaapi.get_func(ea) | |
if not f: | |
# create function | |
idc.MakeFunction(ea, idaapi.BADADDR) | |
# get the flags | |
f = idc.GetFlags(caller) | |
# no code there? | |
if not isCode(f): | |
MakeCode(caller) | |
callers.append(Result(caller, sp)) | |
# | |
return (True, callers) | |
# ----------------------------------------------------------------------- | |
# Chooser class | |
class CallStackWalkChoose(Choose): | |
def __init__(self, list, title): | |
Choose.__init__(self, list, title) | |
self.width = 250 | |
def enter(self, n): | |
o = self.list[n-1] | |
idc.Jump(o.caller) | |
# ----------------------------------------------------------------------- | |
def main(): | |
if not idaapi.is_debugger_on(): | |
idc.Warning("Please run the process first!") | |
return | |
if idaapi.get_process_state() != -1: | |
idc.Warning("Please suspend the debugger first!") | |
return | |
# only avail from IdaPython r232 | |
if hasattr(idaapi, "NearestName"): | |
# get all debug names | |
dn = idaapi.get_debug_names(idaapi.cvar.inf.minEA, idaapi.cvar.inf.maxEA) | |
# initiate a nearest name search (using debug names) | |
nn = idaapi.NearestName(dn) | |
else: | |
nn = None | |
ret, callstack = CallStackWalk(nn) | |
if ret: | |
title = "Call stack walker (thread %X)" % (GetCurrentThreadId()) | |
idaapi.close_chooser(title) | |
c = CallStackWalkChoose(callstack, title) | |
c.choose() | |
else: | |
idc.Warning("Failed to walk the stack:" + callstack) | |
# ----------------------------------------------------------------------- | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment