james91b / ida_ipython

An IDA Pro Plugin for embedding an IPython Kernel
MIT License
252 stars 46 forks source link

Adding IPython Magics #26

Open tmr232 opened 8 years ago

tmr232 commented 8 years ago

Trying to add IPython magics I came to a problem - it seems that I have to add them from the IPython console itself. Is there any other way to do it in the context of the plugin?

Being able to add magics from other plugins will be quite powerful.

james91b commented 8 years ago

IPython magic functions should be added to the current notebook using the load_ext magic in IPython (is that what you are referring to?). As long as the file is on the python path it should all just load fine.

Here is a magic extension that i was working on some time ago. It has some magics for the debugger, it is still incomplete and hacky atm.

import idautils
import idc
import collections
import idaapi
import idautils
from idautils import GetDataList
from IPython.core.magic import Magics, magics_class, cell_magic, line_cell_magic, line_magic
import ast
import itertools
from IPython.core import magic_arguments
from IPython import display
import sys

getmem_func = {
    1: idc.Byte,
    2: idc.Word,
    4: idc.Dword,
    8: idc.Qword
}

breakpoints = {}

def frame_struct(props):
    return ((props & idaapi.SF_FRAME) != 0)

def display_frame(stack_ptr, func_ea):
    func = idaapi.get_func(func_ea)
    start_ea = stack_ptr - func.frsize
    return display_type(start_ea, idc.GetFrame(func_ea))

def display_type(ea, sid):
    members = idautils.StructMembers(sid)
    out = ""
    for offset, name, size in members:
        if size in getmem_func:
            out += "{} 0x{:X}\n".format(name, getmem_func[size](ea+offset))
        else:
            out += "{} 0x{:X} No idea just getting DWORD\n".format(name, Dword(ea+offset))
    return out

def bp(ea, func, hardware=False):
    breakpoints[ea] = func
    # bp_flags = idc.GetBptAttr(ea, idc.BPTATTR_FLAGS)
    if hardware:
        idc.AddBptEx(ea, 4, idc.BPT_RDWR)
        idc.SetBptAttr(ea, idc.BPTATTR_FLAGS, idc.GetBptAttr(ea, idc.BPTATTR_FLAGS) | idc.BPT_TRACE)
    else:
        idc.AddBpt(ea)

    idc.SetBptCnd(ea, "dbghelp.call_breakpoint({})".format(ea))

def bc(ea_or_index):
    if ea_or_index in breakpoints:
        del breakpoints[ea_or_index]
    bp_successfully_deleted = idc.DelBpt(ea_or_index)
    if not bp_successfully_deleted:
        addr = idc.GetBptEA(ea_or_index)
        if addr != idc.BADADDR:
            bc(addr)

def list_breakpoints():
    return iter(int(idc.GetBptEA(i)) for i in xrange(idc.GetBptQty()))

def clear_all_breakpoints():
    for _, addr in enumerate(list_breakpoints()):
        bc(addr)

def call_breakpoint(addr):
    return breakpoints[addr]()

# -----------------------------------------------------------------------
# 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 prev_call_insn(ea):
    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 executable_segment(seg):
    return seg and ((seg.perm & idaapi.SEGPERM_EXEC) == 1)

def call_stack(esp):
    stack = []
    stack_seg = idaapi.getseg(esp)
    word_size = 2 ** (stack_seg.bitness + 1)
    start_ea = esp - word_size
    end_ea = stack_seg.endEA

    for stack_entry in xrange(start_ea, end_ea, word_size):
        ptr = idautils.GetDataList(stack_entry, 1, word_size).next()
        seg = idaapi.getseg(ptr)

        if not executable_segment(seg):
            continue

        call_insn = prev_call_insn(ptr)
        if call_insn:
            stack.append(call_insn)

    return stack

def register_values(regs):
    reg_vals = []
    for reg in regs:
        reg_vals.append(idc.GetRegValue(str(reg.strip())))
    return reg_vals

class PauseProgramExeception(Exception): pass

@magics_class
class DbgMagics(Magics):
    BC_CLEAR_ALL = '*'

    @line_cell_magic
    def bp(self, line='', cell=None, local_ns=None):
        #print magic_arguments.parse_argstring(self.bp, line)
        opts, stmt = self.parse_options(line,
                                        'rh',
                                        posix=False,
                                        strict=False)

        refresh = 'r' in opts
        hardware = 'h' in opts

        glob = self.shell.user_ns
        transform  = self.shell.input_splitter.transform_cell

        expr_ast = ast.Expression(ast.parse(transform(stmt)).body[0].value)
        bp_addr = eval(compile(expr_ast, "<bp>", "eval"), glob, local_ns)

        ast_body = ast.parse(transform(cell))
        ast_expr = self.shell.transform_ast(ast_body)
        code = compile(ast_expr, "<bp body {:X}>".format(bp_addr), "exec")

        def bp_func():
            try:
                if refresh:
                    display.clear_output(wait=True)
                exec code in glob, local_ns
                sys.stdout.flush()
            except PauseProgramExeception:
                return True

        bp(bp_addr, bp_func, hardware=hardware)

    @line_magic
    def bl(self, line):
        for i, bp in enumerate(list_breakpoints()):
            print "{} {:08X}".format(i, bp)

    @line_magic
    def bc(self, line, local_ns=None):
        stripped_line = str(line).strip()
        if stripped_line == self.BC_CLEAR_ALL:
            clear_all_breakpoints()
        else:
            glob = self.shell.user_ns
            expr_ast = ast.Expression(ast.parse(line).body[0].value)
            bp_addr = eval(compile(expr_ast, "<bp>", "eval"), glob, local_ns)
            bc(int(bp_addr))

    @line_magic
    def jump(self, line, local_ns=None):
        glob = self.shell.user_ns
        expr_ast = ast.Expression(ast.parse(line).body[0].value)
        addr = eval(compile(expr_ast, "<bp>", "eval"), glob, local_ns)
        idc.Jump(int(addr))

    @magic_arguments.magic_arguments()
    @magic_arguments.argument('--pretty', '-p', action='store_true',
                              default=False,
        help="""pretty print the registers to stdout"""
    )
    @magic_arguments.argument('regs', nargs='*',
        help="""List of registers to show"""
    )
    @line_magic
    def r(self, line):
        args = magic_arguments.parse_argstring(self.r, line)
        reg_vals = register_values(args.regs)
        if args.pretty:
            for name, val in itertools.izip(args.regs, reg_vals):
                print "{} {:X}".format(name.upper(), val)
        else:
            return reg_vals if len(reg_vals) > 1 else reg_vals[0]

def load_ipython_extension(ipython):
    ipython.register_magics(DbgMagics)
tmr232 commented 8 years ago

Well, I want to be able to add magics using other plugins, without manually running anything from the IPython shell/notebook. I am not sure it is possible, though.

james91b commented 8 years ago

You would need to be careful, because magic functions are shared in a global namespace. However you could do something like

import idaapi
import ipythonEmbed #add some error handling when this does not exist

class test_plugin_t(idaapi.plugin_t):
    flags = 0
    comment = "Test plugin"

    help = "This is help"
    wanted_name = "Test plugin"
    wanted_hotkey = "Alt-J"

    def init(self):
        idaapi.msg('test_plugin_t:init\n')
        if ipythonEmbed.kernel_app:
            idaapi.msg('test_plugin_t:keep\n')
            #the extension must exist on the PythonPath e.g. IDA Pro\Python directory
            ipythonEmbed.kernel_app.shell.extension_manager.load_extension("dbghelp")
            #Add some error handling ect.
            return idaapi.PLUGIN_KEEP
        else:
            #Failed to load
            idaapi.msg('test_plugin_t:fail\n')
            return idaapi.PLUGIN_SKIP

    def run(self, arg):
        idaapi.msg('test_plugin_t:run\n')

    def term(self):
        idaapi.msg('test_plugin_t:term\n')

def PLUGIN_ENTRY():
    return test_plugin_t()