Skip to content

synacktiv/bip

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bip

Bip is a project which aims to simplify the usage of python for interacting with IDA. Its main goals are to facilitate the usage of python in the interactive console of IDA and the writing of plugins. In a more general way the goal is to automate recurrent tasks done through the python API. Bip is also developed to provide a more object oriented, a "python-like" API and a real documentation.

This code is not complete, and a lot of features are still missing. Development is prioritized on what people ask for and what the developers use, so do not hesitate to make PR, Feature Request and Issues (including for the documentation).

The documentation is available in the RST format (and can be compiled using sphinx) in the docs/ directory, it is also available online.

  • Current IDA version: IDA 7.5SP1 and Python 2.7 or 3.8
  • Last Bip Version: 1.0

Installation

This installation has been tested only on Windows and Linux: python install.py.

It is possible to use an optional --dest argument to install in a particular folder:

usage: install.py [-h] [--dest DEST]

optional arguments:
  -h, --help   show this help message and exit
  --dest DEST  Destination folder where to install Bip

This installer does not install any plugins by default, but simply the core of Bip. By default the destination folder is the one used by IDA locally (%APPDATA%\Hex-Rays\IDA Pro\ for Windows and $HOME/.idapro for Linux and MacOSX).

Overview

This overview has a goal to show how the most usual operations can be done, it is far from being complete. All functions and objects in Bip are documented using doc string so just use help(BipClass) and help(obj.bipmethod) to get the doc in your shell.

Base

The module bip.base contains most of the basic features for interfacing with IDA. In practice this is mainly the disassembler part of IDA, this includes: manipulation of instructions, functions, basic blocks, operands, data, xrefs, structures, types, ...

Instructions / Operands

The classes bip.base.BipInstr and bip.base.BipOperand:

>>> from bip.base import *
>>> i = BipInstr() # BipInstr is the base class for representing an instruction
>>> i # by default the address on the screen is taken
BipInstr: 0x1800D324B (mov     rcx, r13)
>>> i2 = BipInstr(0x01800D3242) # pass the address in argument
>>> i2
BipInstr: 0x1800D3242 (mov     r8d, 8)
>>> i2.next # access next instruction, previous with i2.prev
BipInstr: 0x1800D3248 (mov     rdx, r14)
>>> l = [i3 for i3 in BipInstr.iter_all()] # l contains the list of all BipInstruction of the database, iter_all produces a generator object
>>> i.ea # access the address
6443315787
>>> i.mnem # mnemonic representation
mov
>>> i.ops # access to the operands
[<bip.base.operand.BipOperand object at 0x0000022B0291DA90>, <bip.base.operand.BipOperand object at 0x0000022B0291DA58>]
>>> i.ops[0].str # string representation of an operand
rcx
>>> i.bytes # bytes in the instruction
[73L, 139L, 205L]
>>> i.size # number of bytes of this instruction
3
>>> i.comment = "hello" # set a comment, rcomment for the repeatable comments
>>> i
BipInstr: 0x1800D324B (mov     rcx, r13; hello)
>>> i.comment # get a comment
hello
>>> i.func # access to the function
Func: RtlQueryProcessLockInformation (0x1800D2FF0)
>>> i.block # access to basic block
BipBlock: 0x1800D3242 (from Func: RtlQueryProcessLockInformation (0x1800D2FF0))

Function / Basic block

The classes bip.base.BipFunction and bip.base.BipBlock:

>>> from bip.base import *
>>> f = BipFunction() # Get the function, screen address used if not provided
>>> f
Func: RtlQueryProcessLockInformation (0x1800D2FF0)
>>> f2 = BipFunction(0x0018010E975) # provide an address, not necessary the first one
>>> f2
Func: sub_18010E968 (0x18010E968)
>>> f == f2 # compare two functions
False
>>> f == BipFunction(0x001800D3021)
True
>>> hex(f.ea) # start address
0x1800d2ff0L
>>> hex(f.end) # end address
0x1800d3284L
>>> f = BipFunction.get_by_name("RtlQueryProcessLockInformation") # fetch the function from its name
>>> f.name # get and set the name
RtlQueryProcessLockInformation
>>> f.name = "test"
>>> f.name
test
>>> f.size # number of bytes in the function
660
>>> f.bytes # bytes of the function
[72L, ..., 255L]
>>> f.callees # list of functions called by this function
[<bip.base.func.BipFunction object at 0x0000022B0291DD30>, ..., <bip.base.func.BipFunction object at 0x0000022B045487F0>]
>>> f.callers # list of functions which call this function
[<bip.base.func.BipFunction object at 0x0000022B04544048>]
>>> f.instr # list of instructions in the function
[<bip.base.instr.BipInstr object at 0x0000022B0291DB00>, ..., <bip.base.instr.BipInstr object at 0x0000022B0454D080>]
>>> f.comment = "welcome to bip" # comment of the function, rcomment for repeatable ones
>>> f.comment
welcome to bip
>>> f.does_return # does this function return ?
True
>>> BipFunction.iter_all() # allows to iter on all functions defined in the database
<generator object iter_all at 0x0000022B029231F8>
>>> f.nb_blocks # number of basic blocks
33
>>> f.blocks # list of blocks
[<bip.base.block.BipBlock object at 0x0000022B04544D68>, ..., <bip.base.block.BipBlock object at 0x0000022B04552240>]
>>> f.blocks[5] # access the basic block 5, could be done with BipBlock(addr)
BipBlock: 0x1800D306E (from Func: test (0x1800D2FF0))
>>> f.blocks[5].func # link back to the function
Func: test (0x1800D2FF0)
>>> f.blocks[5].instr # list of instructions in the block
[<bip.base.instr.BipInstr object at 0x0000022B04544710>, ..., <bip.base.instr.BipInstr object at 0x0000022B0291DB00>]
>>> f.blocks[5].pred # predecessor blocks, blocks where control flow lead to this one
[<bip.base.block.BipBlock object at 0x0000022B04544D68>]
>>> f.blocks[5].succ # successor blocks
[<bip.base.block.BipBlock object at 0x0000022B04544710>, <bip.base.block.BipBlock object at 0x0000022B04544438>]
>>> f.blocks[5].is_ret # is this block containing a return
False

Data

The class bip.base.BipData:

>>> from bip.base import *
>>> d = BipData(0x000180110068) # .rdata:0000000180110068 bip_ex          dq offset unk_180110DE0
>>> d
BipData at 0x180110068 = 0x180110DE0 (size=8)
>>> d.name # Name of the symbol if any
bip_ex
>>> d.is_word # is it a word
False
>>> d.is_qword # is it a qword
True
>>> hex(d.value) # value at that address, this take into account the basic type (byte, word, dword, qword) defined in IDA
0x180110de0L
>>> hex(d.ea) # address
0x180110068L
>>> d.comment = "example" # comment as before
>>> d.comment
example
>>> d.value = 0xAABBCCDD # change the value
>>> hex(d.value)
0xaabbccddL
>>> d.bytes # get the bytes, as before
[221L, 204L, 187L, 170L, 0L, 0L, 0L, 0L]
>>> hex(d.original_value) # get the original value before modification
0x180110de0L
>>> d.bytes = [0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0] # patch the bytes
>>> hex(d.value) # get the value
0x44332211L
>>> BipData.iter_heads() # iter on "heads" of the IDB, heads are defined data in the IDB
<generator object iter_heads at 0x0000022B02923240>
>>> hex(BipData.get_dword(0x0180110078)) # staticmethod for reading value at an address
0x60004L
>>> BipData.set_byte(0x0180110078, 0xAA) # static method for modifying a value at an address
>>> hex(BipData.get_qword(0x0180110078))
0x600aaL

Element

In Bip most basic objects inherit from the same classes: BipBaseElt which is the most basic one, BipRefElt which includes all the objects which can have xrefs (including structures (BipStruct) and structure members (BStructMember), see below), BipElt which represents all elements which have an address in the IDA DataBase (idb), including BipData and BipInstr (it is this class which implements the properties: comment, name, bytes, ...).

It is possible to use the functions GetElt and GetEltByName to get the right basic element from an address or a name representing a location in the binary.

>>> from bip.base import *
>>> GetElt() # get the element at current address, in this case return a BipData object
BipData at 0x180110068 = 0xAABBCCDD (size=8)
>>> GetElt(0x00180110078) # get the element at the address 0x00180110078
BipData at 0x180110078 = 0xAA (size=1)
>>> GetElt(0x1800D2FF0) # in this case it returns an BipInstr object because this is code
BipInstr: 0x1800D2FF0 (mov     rax, rsp)
>>> GetEltByName("bip_ex") # Get using a name and not an address
BipData at 0x180110068 = 0xAABBCCDD (size=8)
>>> isinstance(GetElt(0x1800D2FF0), BipInstr) # test if that element is an instruction ?
True
>>> GetElt(0x1800D2FF0).is_code # are we on code ? same for is_data; do not work for struct
True
>>> isinstance(GetElt(0x1800D2FF0), BipData) # or data ?
False

Some static functions are provided to search elements in the database:

>>> from bip.base import *
>>> GetElt()
BipInstr: 0x1800D3248 (mov     rdx, r14)
>>> BipElt.next_code() # find next code elt from current addres or addr passed as arg
BipInstr: 0x1800D324B (mov     rcx, r13)
>>> BipElt.next_code(down=False) # find prev code element
BipInstr: 0x1800D3242 (mov     r8d, 8)
>>> BipElt.next_data() # find next data elt from current address or addr passed as arg
BipData at 0x1800D3284 = 0xCC (size=1)
>>> BipElt.next_data(down=False) # find previous data element
BipData at 0x1800D2FE1 = 0xCC (size=1)
>>> hex(BipElt.next_data_addr(down=False)) # find address of the previous data element
0x1800d2fe1L
>>> BipElt.next_unknown() # same for unknown, which are not typed element of IDA and are considered data by Bip
BipData at 0x180110000 = 0xE (size=1)
>>> BipElt.next_defined() # opposite of unknown: data or code
BipInstr: 0x1800D324B (mov     rcx, r13)
>>> BipElt.search_bytes("49 ? CD", 0x1800D3248) # search for byte sequence (ignore the current position by default)
BipInstr: 0x1800D324B (mov     rcx, r13)

Xref

All elements which inherit from BipRefElt (BipInstr, BipData, BipStruct, ...) and some other (in particular BipFunction) contain methods which allow to access xrefs. They are represented by the BipXref objects which have a src (origin of the xref) and a dst (destination of the xref).

>>> from bip.base import *
>>> i = BipInstr(0x01800D3063)
>>> i # example with instruction but works the same with BipData
BipInstr: 0x1800D3063 (cmp     r15, [rsp+98h+var_58])
>>> i.xTo # List of xref which point on this instruction
[<bip.base.xref.BipXref object at 0x0000022B04544438>, <bip.base.xref.BipXref object at 0x0000022B045447F0>]
>>> i.xTo[0].src # previous instruction
BipInstr: 0x1800D305E (mov     [rsp+98h+var_78], rsi)
>>> i.xTo[0].is_ordinaryflow # is this an ordinary flow between to instruction (not jmp or call)
True
>>> i.xTo[1].src # jmp to instruction i at 0x1800D3063
BipInstr: 0x1800D3222 (jmp     loc_1800D3063)
>>> i.xTo[1].is_jmp # is this xref because of a jmp ?
True
>>> i.xEaTo # bypass the xref objects and get the address directly
[6443315294L, 6443315746L]
>>> i.xEltTo # bypass the xref objects and get the elements directly, will list BipData if any
[<bip.base.instr.BipInstr object at 0x0000022B045447F0>, <bip.base.instr.BipInstr object at 0x0000022B04544978>]
>>> i.xCodeTo # bypass the xref objects and get the instr directly, if a BipData was pointed at this address it will not be listed
[<bip.base.instr.BipInstr object at 0x0000022B04544438>, <bip.base.instr.BipInstr object at 0x0000022B0291DD30>]
>>> i.xFrom # same but for coming from this instruction
[<bip.base.xref.BipXref object at 0x0000022B04544D68>]
>>> i.xFrom[0]
<bip.base.xref.BipXref object at 0x0000022B04544438>
>>> i.xFrom[0].dst # next instruction
BipInstr: 0x1800D3068 (jz      loc_1800D3227)
>>> i.xFrom[0].src # current instruction
BipInstr: 0x1800D3063 (cmp     r15, [rsp+98h+var_58])
>>> hex(i.xFrom[0].dst_ea) # address of the next instruction
0x1800D3068L
>>> i.xFrom[0].is_codepath # this is a normal code path (include jmp and call)
True
>>> i.xFrom[0].is_call # is this because of a call ?
False
>>> f = BipFunction()
>>> f
Func: RtlQueryProcessLockInformation (0x1800D2FF0)
>>> f.xTo # works also for functions, but only with To, not with the From
[<bip.base.xref.BipXref object at 0x000001D95529EB00>, <bip.base.xref.BipXref object at 0x000001D95529EB70>, <bip.base.xref.BipXref object at 0x000001D95529EBE0>, <bip.base.xref.BipXref object at 0x000001D95529EC88>]
>>> f.xEltTo # here we have 3 data references to this function
[<bip.base.instr.BipInstr object at 0x000001D95529EE48>, <bip.base.data.BipData object at 0x000001D95529EEF0>, <bip.base.data.BipData object at 0x000001D95529EF28>, <bip.base.data.BipData object at 0x000001D95529EF60>]
>>> f.xCodeTo # but only one instruction
[<bip.base.instr.BipInstr object at 0x000001D95529EC88>]

Struct

Manipulating struct (BipStruct) and members (BStructMember):

>>> from bip.base import *
>>> st = BipStruct.get("EXCEPTION_RECORD") # Structs are accessed by using get and their name
>>> st # BipStruct object
Struct: EXCEPTION_RECORD (size=0x98)
>>> st.comment = "struct comment"
>>> st.comment
struct comment
>>> st.name
EXCEPTION_RECORD
>>> st.size
152
>>> st["ExceptionFlags"] # access to the BStructMember by their name
Member: EXCEPTION_RECORD.ExceptionFlags (offset=0x4, size=0x4)
>>> st[8] # or by their offset, this is *not* the entry number 8!!!
Member: EXCEPTION_RECORD.ExceptionRecord (offset=0x8, size=0x8)
>>> st[2] # offset does not need to be the first one
Member: EXCEPTION_RECORD.ExceptionCode (offset=0x0, size=0x4)
>>> st.members # list of members
[<bip.base.struct.BStructMember object at 0x000001D95529EEF0>, ..., <bip.base.struct.BStructMember object at 0x000001D95536DF28>]
>>> st[0].name
ExceptionCode
>>> st[0].fullname
EXCEPTION_RECORD.ExceptionCode
>>> st[0].size
4
>>> st[0].struct
Struct: EXCEPTION_RECORD (size=0x98)
>>> st[0].comment = "member comment"
>>> st[0].comment
member comment
>>> st[8].xEltTo # BStructMember et BipStruct have xrefs
[<bip.base.instr.BipInstr object at 0x000001D95536DD30>, <bip.base.instr.BipInstr object at 0x000001D95536D9E8>]
>>> st[8].xEltTo[0]
BipInstr: 0x1800A0720 (mov     [rsp+538h+ExceptionRecord.ExceptionRecord], r10)

Creating struct, adding members and nested structure:

>>> from bip.base import *
>>> st = BipStruct.create("NewStruct") # create a new structure
>>> st
Struct: NewStruct (size=0x0)
>>> st.add("NewField", 4) # add a new member named "NewField" of size 4
Member: NewStruct.NewField (offset=0x0, size=0x4)
>>> st.add("NewQword", 8)
Member: NewStruct.NewQword (offset=0x4, size=0x8)
>>> st
Struct: NewStruct (size=0xC)
>>> st.add("struct_nested", 1)
Member: NewStruct.struct_nested (offset=0xC, size=0x1)
>>> st["struct_nested"].type = BipType.from_c("EXCEPTION_RECORD") # changing the type of member struct_nested to struct EXCEPTION_RECORD
>>> st["struct_nested"]
Member: NewStruct.struct_nested (offset=0xC, size=0x98)
>>> st["struct_nested"].is_nested # is this a nested structure ?
True
>>> st["struct_nested"].nested_struct # getting the nested structure
Struct: EXCEPTION_RECORD (size=0x98)

Types

IDA uses extensively types in hexrays but also in the base API for defining types of data, variables and so on. In Bip the different types inherit from the same class BipType. This class offers some basic methods common to all types and subclasses (class starting by BType) can define more specific ones.

The types should be seen as a recursive structure: a void * is a BTypePtr containing a BTypeVoid structure. For a list of the different types implemented in Bip see the documentation.

>>> from bip.base import *
>>> pv = BipType.from_c("void *") # from_c is the easiest way to create a type
>>> pv
<bip.base.biptype.BTypePtr object at 0x000001D95536DDD8>
>>> pv.size # ptr on x64 is 8 bytes
8
>>> pv.str # C string representation
void *
>>> pv.is_named # this type is not named
False
>>> pv.pointed # type below the pointer (recursive)
<bip.base.biptype.BTypeVoid object at 0x000001D95536DF60>
>>> pv.children # list of type pointed
[<bip.base.biptype.BTypeVoid object at 0x000001D95536DEB8>]
>>> d = BipData(0x000180110068)
>>> d.type # access directly to the type at the address
<bip.base.biptype.BTypePtr object at 0x000001D95536D9E8>
>>> d.type.str
void *
>>> ps = BipType.from_c("EXCEPTION_RECORD *")
>>> ps.pointed # type for struct EXCEPTION_RECORD
<bip.base.biptype.BTypeStruct object at 0x000001D95536DD30>
>>> ps.pointed.is_named # this one is named
True
>>> ps.pointed.name
EXCEPTION_RECORD
>>> ps.set_at(d.ea) # set the type ps at address d.ea
>>> d.type.str # the type has indeed changed
EXCEPTION_RECORD *
>>> d.type = pv # rolling it back
>>> d.type.str
void *
>>> BipType.get_at(d.ea) # Possible to directly get the type with get_at(address)
<bip.base.biptype.BTypePtr object at 0x000001D95536DEB8>

Hexrays

The module bip.hexrays contains the features linked to the decompiler provided by IDA.

Functions / local variables

Hexrays functions are represented by the HxCFunc objects and local variable by the HxLvar objects:

>>> HxCFunc.from_addr() # HxCFunc represents a decompiled function
<bip.hexrays.hx_cfunc.HxCFunc object at 0x00000278AE80C860>
>>> hf = BipFunction().hxfunc # accessible from a "normal function"
>>> hex(hf.ea) # address of the function
0x1800d2ff0L
>>> hf.args # list of the arguments as HxLvar objects
[<bip.hexrays.hx_lvar.HxLvar object at 0x00000278AFDAACF8>]
>>> hf.lvars # list of all local variables (including args)
[<bip.hexrays.hx_lvar.HxLvar object at 0x00000278AFDAAB70>, ..., <bip.hexrays.hx_lvar.HxLvar object at 0x00000278AFDAF4E0>]
>>> lv = hf.lvars[0] # getting the first one
>>> lv
LVAR(name=a1, size=8, type=<bip.base.biptype.BTypeInt object at 0x00000278AFDAAFD0>)
>>> lv.name # getting name of lvar
a1
>>> lv.is_arg # is this variable an argument ?
True
>>> lv.name = "thisisthefirstarg" # changing name of the lvar
>>> lv
>>> lv.type = BipType.from_c("void *") # changing the type
>>> lv.comment = "new comment" # adding a comment
>>> lv.size # getting the size
8

CNode / Visitors

Hexrays allows to manipulate the AST it produces, this is a particularly useful feature as it allows to make static analysis at a way higher level. Bip defines CNode which represents a node of the AST, each type of node is represented by a subclass of CNode. All types of node have child nodes except CNodeExprFinal which are the leaf of the AST. Two main types of nodes exist CNodeExpr (expressions) and CNodeStmt (statements). Statements correspond to the C Statements: if, while, ... , expressions are everything else. Statements can have children statements or expressions while expressions can only have expression children.

A list of all the different types of nodes and more details on what they do and how to write a visitor is available in the documentation.

Directly accessing the nodes:

>>> hf = HxCFunc.from_addr() # get the HxCFunc
>>> rn = hf.root_node # accessing the root node of the function
>>> rn # root node is always a CNodeStmtBlock
CNodeStmtBlock(ea=0x1800D3006, stmt_children=[<bip.hexrays.cnode.CNodeStmtExpr object at 0x00000278AFDAADD8>, ..., <bip.hexrays.cnode.CNodeStmtReturn object at 0x00000278B16355F8>])
>>> hex(rn.ea) # address of the root node, after the function prolog
0x1800d3006L
>>> rn.has_parent # root node does not have parent
False
>>> rn.expr_children # this node does not have expression statements
[]
>>> ste = rn.stmt_children[0] # getting the first statement children
>>> ste # CNodeStmtExpr contain one child expression
CNodeStmtExpr(ea=0x1800D3006, value=CNodeExprAsg(ea=0x1800D3006, ops=[<bip.hexrays.cnode.CNodeExprVar object at 0x00000278AFDAADD8>, <bip.hexrays.cnode.CNodeExprVar object at 0x00000278B1637080>]))
>>> ste.parent # the parent is the root node
CNodeStmtBlock(ea=0x1800D3006, stmt_children=[<bip.hexrays.cnode.CNodeStmtExpr object at 0x00000278B1637048>, ..., <bip.hexrays.cnode.CNodeStmtReturn object at 0x00000278B16376D8>])
>>> a = ste.value # getting the expression of the node
>>> a # Asg is an assignement
CNodeExprAsg(ea=0x1800D3006, ops=[<bip.hexrays.cnode.CNodeExprVar object at 0x00000278AFDAADD8>, <bip.hexrays.cnode.CNodeExprVar object at 0x00000278B1637080>])
>>> a.first_op # first operand of the assignement is a lvar, lvar are leaf
CNodeExprVar(ea=0xFFFFFFFFFFFFFFFF, value=1)
>>> a.first_op.lvar # get the lvar object
LVAR(name=v1, size=8, type=<bip.base.biptype.BTypeInt object at 0x00000278B16390B8>)
>>> a.ops # list all operands of the expression
[<bip.hexrays.cnode.CNodeExprVar object at 0x00000278AFDAADD8>, <bip.hexrays.cnode.CNodeExprVar object at 0x00000278B1639080>]
>>> a.ops[1] # getting the second operand, also a lvar
CNodeExprVar(ea=0xFFFFFFFFFFFFFFFF, value=0)
>>> hex(a.ops[1].closest_ea) # lvar have no position in the ASM, but possible to take the one of the parents
0x1800d3006L

The previous code show how to get a value and manipulate nodes quickly. To do an analysis it is easier to use visitors on the complete function. HxCFunc.visit_cnode allows to visit all the nodes in a function with a callback, HxCFunc.visit_cnode_filterlist allows to visit only nodes of a certain type by passing a list of the node classes.

This script is an example to visit a function and get the format string passed to a printk function. It locates the call to printk, gets the address of the first argument, gets the string and adds a comment in both hexrays and the assembly:

from bip import *

"""
    Search for all call to printk, if possible gets the string and adds
    it in comments at the level of the call.
"""

def is_call_to_printk(cn):
    """
        Check if the node object represent a call to the function ``printk``.

        :param cn: A :class:`CNodeExprCall` object.
        :return: True if it is a call to printk, False otherwise
    """
    f = cn.caller_func
    return f is not None and f.name == "printk"

def visit_call_printk(cn):
    """
        Visitor for call node which will check if a node is a call to
        ``printk`` and add the string in comment if possible.

        :param cn: A :class:`CNodeExprCall` object.
    """
    # check if it calls to printk
    # For more perf. we would want to use xref to printk and checks of
    #   the address of the node
    if not is_call_to_printk(cn): # not a call to printk: ignore
        return
    if cn.number_args < 1: # not enough args
        print("Not enough args at 0x{:X}".format(cn.closest_ea))
        return
    cnr = cn.get_arg(0).ignore_cast # get the arg
    # if we have a ref (&global) we want the object under
    if isinstance(cnr, CNodeExprRef):
        cnr = cnr.ops[0].ignore_cast
    # if this is not a global object we ignore it
    if not isinstance(cnr, CNodeExprObj):
        print("Not an object at 0x{:X}".format(cn.closest_ea))
        return
    ea = cnr.value # get the address of the object
    s = None
    try:
        s = BipData.get_cstring(ea + 2) # get the string
    except Exception:
        pass
    if s is None or s == "":
        print("Invalid string at 0x{:X}".format(cn.closest_ea))
        return
    s = s.strip() # remove \n
    # add comment both in hexrays and in asm view
    cn.hxcfunc.add_cmt(cn.closest_ea, s)
    GetElt(cn.closest_ea).comment = s

# Final function which takes the address of a function and comments the call
#   to printk
def printk_handler(eafunc):
    hf = HxCFunc.from_addr(eafunc) # get the hexrays function
    hf.visit_cnode_filterlist(visit_call_printk, [CNodeExprCall]) # visit only the call nodes

While visitors are convenient (and "fast"), Bip also exposes methods to directly get the CNode objects as a list. The methods HxCFunc.get_cnode_filter and HxCFunc.get_cnode_filter_type allow to avoid having a visitor function and make it easier to manipulate the hexrays API. It is also worth noting that all visitors functions provided by HxCFunc objects are also available directly in CNode objects to visit only a sub-tree of the full AST.

Plugins

Plugins using Bip should all inherit from the class BipPlugin. Those plugins are different from the IDA plugins and are loaded and called by the BipPluginManager. Each plugin is identified by its class name and those should be unique. Bip can be used with standard plugin but most of the bip.gui implementation is linked to the use of BipPlugin. For more information about plugins and internals see the documentation.

Here is a simple plugin example:

from bip.gui import * # BipPlugin is defined in the bip.gui module

class ExPlugin(BipPlugin):
    # inherit from BipPlugin, all plugin should be instantiated only once
    # this should be done by the plugin manager, not "by hand"

    @classmethod
    def to_load(cls): # allow to test if the plugin apply, this MUST be a classmethod
        return True # always loading

    @shortcut("Ctrl-H") # add a shortcut as a decorator, will call the method below
    @shortcut("Ctrl-5") # add an other one
    @menu("Bip/MyPluginExample/", "ExPlugin Action!") # add a menu entry named "ExPlugin Action!", default is the method name
    def action_with_shortcut(self):
        print(self) # this is the ExPlugin object
        print("In ExPlugin action !")# code here

bpm = get_plugin_manager() # get the BipPluginManager object
bpm.addld_plugin("ExPlugin", ExPlugin) # ask the BipPluginManager to load the plugin
# plugins in ``bipplugin`` folder will be loaded automatically and do not need those lines

The menu decorator will automatically create the MyPluginExample menu entry in the Bip top level menu entry (which is created by the BipPluginManager), creating an entry in the Edit/Plugins/ directory may not work because of how the entry of this submenu are created by IDA.

A plugin can expose methods which another plugin wants to call or directly from the console. A plugin should not be directly instantiated, it is the BipPluginManager which is in charge of loading it. To get a BipPlugin object, it should be requested to the plugin manager:

from bip.gui import *
bpm = get_plugin_manager() # get the BipPluginManager object
bpm
# <bip.gui.pluginmanager.BipPluginManager object at 0x000001EFE42D68D0>
tp = bpm["TstPlugin"] # get the plugin object name TstPlugin
tp # can also be recuperated by passing directly the class
# <__plugins__tst_plg.TstPlugin object at 0x000001EFE42D69B0>
tp.hello() # calling a method of TstPlugin
# hello

For the previous example with printk we could write the following plugin:

class PrintkComs(BipPlugin):

    def printk_handler(self, eafunc):
        """
            Comment all call to printk in a function with the format string
            pass to the printk. Comments are added in both the hexrays and ASM
            view. Works only if the first argument is a global.

            :param eafunc: The addess of the function in which to add the
                comment.
        """
        try:
            hf = HxCFunc.from_addr(eafunc) # get hexray view of the func
        except Exception:
            print("Fail getting the decompile view for function at 0x{:X}".format(eafunc))
            return
        hf.visit_cnode_filterlist(visit_call_printk, [CNodeExprCall]) # visit only on the call

    @shortcut("Ctrl-H")
    @menu("Bip/PrintkCom/", "Comment printk in current function")
    def printk_current(self):
        """
            Add comment for the current function.
        """
        self.printk_handler(Here())

    @menu("Bip/PrintkCom/", "Comment all printk")
    def printk_all(self):
        """
            Add comment for the all the functions in the IDB.
        """
        # get the function which call printk
        f = BipFunction.get_by_name("printk")
        if f is None:
            print("No function named printk")
            return
        for fu in f.callers:
            print("Renaming for {}".format(fu))
            self.printk_handler(fu.ea)

Similar projects

  • sark: "an object-oriented scripting layer written on top of IDAPython".
  • ida-minsc: "a plugin for IDA Pro that assists a user with scripting the IDAPython plugin that is bundled with the disassembler".
  • FIDL: "FLARE IDA Decompiler Library"

Thanks

Some people to thanks:

  • saph: for starting this project.
  • hakril: for the inspiration for the project and his insights on designing it.