cemu-idapython/examples/ex_custdata.py

217 lines
7.7 KiB
Python
Raw Normal View History

# -----------------------------------------------------------------------
# This is an example illustrating how to use custom data types in Python
# (c) Hex-Rays
#
from idaapi import data_type_t, data_format_t, NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE, COLSTR
import struct
import ctypes
import platform
#<pycode(ex_custdata)>
# -----------------------------------------------------------------------
class pascal_data_type(data_type_t):
def __init__(self):
data_type_t.__init__(self, name="py_pascal_string",
value_size = 2, menu_name = "Pascal string",
asm_keyword = "pstr")
def calc_item_size(self, ea, maxsize):
# Custom data types may be used in structure definitions. If this case
# ea is a member id. Check for this situation and return 1
if _idaapi.is_member_id(ea):
return 1
# get the length byte
n = _idaapi.get_byte(ea)
# string too big?
if n > maxsize:
return 0
# ok, accept the string
return n + 1
class pascal_data_format(data_format_t):
FORMAT_NAME = "py_pascal_string_pstr"
def __init__(self):
data_format_t.__init__(self, name=pascal_data_format.FORMAT_NAME)
def printf(self, value, current_ea, operand_num, dtid):
# Take the length byte
n = ord(value[0])
o = ['"']
for ch in value[1:]:
b = ord(ch)
if b < 0x20 or b > 128:
o.append(r'\x%02x' % ord(ch))
else:
o.append(ch)
o.append('"')
return "".join(o)
# -----------------------------------------------------------------------
class simplevm_data_type(data_type_t):
ASM_KEYWORD = "svm_emit"
def __init__(self):
data_type_t.__init__(self,
name="py_simple_vm",
value_size = 1,
menu_name = "SimpleVM",
asm_keyword = simplevm_data_type.ASM_KEYWORD)
def calc_item_size(self, ea, maxsize):
if _idaapi.is_member_id(ea):
return 1
# get the opcode and see if it has an imm
n = 5 if (_idaapi.get_byte(ea) & 3) == 0 else 1
# string too big?
if n > maxsize:
return 0
# ok, accept
return n
class simplevm_data_format(data_format_t):
def __init__(self):
data_format_t.__init__(self,
name="py_simple_vm_format",
menu_name = "SimpleVM")
# Some tables for the disassembler
INST = {1: 'add', 2: 'mul', 3: 'sub', 4: 'xor', 5: 'mov'}
REGS = {1: 'r1', 2: 'r2', 3: 'r3'}
def disasm(self, inst):
"""A simple local disassembler. In reality one can use a full-blown disassembler to render the text"""
opbyte = ord(inst[0])
op = opbyte >> 4
if not (1<=op<=5):
return None
r1 = (opbyte & 0xf) >> 2
r2 = opbyte & 3
sz = 0
if r2 == 0:
if len(inst) != 5:
return None
imm = struct.unpack_from('L', inst, 1)[0]
sz = 5
else:
imm = None
sz = 1
text = "%s %s, %s" % (
COLSTR(simplevm_data_format.INST[op], idaapi.SCOLOR_INSN),
COLSTR(simplevm_data_format.REGS[r1], idaapi.SCOLOR_REG),
COLSTR("0x%08X" % imm, idaapi.SCOLOR_NUMBER) if imm is not None else COLSTR(simplevm_data_format.REGS[r2], idaapi.SCOLOR_REG))
return (sz, text)
def printf(self, value, current_ea, operand_num, dtid):
r = self.disasm(value)
if not r:
return None
if dtid == 0:
return "%s(%s)" % (simplevm_data_type.ASM_KEYWORD, r[1])
return r[1]
# -----------------------------------------------------------------------
# This format will display DWORD values as MAKE_DWORD(0xHI, 0xLO)
class makedword_data_format(data_format_t):
def __init__(self):
data_format_t.__init__(self,
name="py_makedword",
value_size = 4,
menu_name = "Make DWORD")
def printf(self, value, current_ea, operand_num, dtid):
if len(value) != 4: return None
w1 = struct.unpack_from("H", value, 0)[0]
w2 = struct.unpack_from("H", value, 2)[0]
return "MAKE_DWORD(0x%04X, 0x%04X)" % (w2, w1)
# -----------------------------------------------------------------------
# This format will try to load a resource string given a number
# So instead of displaying:
# push 66h
# call message_box_from_rsrc_string
# It can be rendered as;
# push RSRC("The message")
# call message_box_from_rsrc_string
#
# The get_rsrc_string() is not optimal since it loads/unloads the
# DLL each time for a new string. It can be improved in many ways.
class rsrc_string_format(data_format_t):
def __init__(self):
data_format_t.__init__(self,
name="py_w32rsrcstring",
value_size = 1,
menu_name = "Resource string")
self.cache_node = idaapi.netnode("$ py_w32rsrcstring", 0, 1)
def get_rsrc_string(self, fn, id):
"""
Simple method that loads the input file as a DLL with LOAD_LIBRARY_AS_DATAFILE flag.
It then tries to LoadString()
"""
k32 = ctypes.windll.kernel32
u32 = ctypes.windll.user32
hinst = k32.LoadLibraryExA(fn, 0, 0x2)
if hinst == 0:
return ""
buf = ctypes.create_string_buffer(1024)
r = u32.LoadStringA(hinst, id, buf, 1024-1)
k32.FreeLibrary(hinst)
return buf.value if r else ""
def printf(self, value, current_ea, operand_num, dtid):
# Is it already cached?
val = self.cache_node.supval(current_ea)
# Not cached?
if val == None:
# Retrieve it
num = idaapi.struct_unpack(value)
val = self.get_rsrc_string(idaapi.get_input_file_path(), num)
# Cache it
self.cache_node.supset(current_ea, val)
# Failed to retrieve?
if val == "" or val == "\x00":
return None
# Return the format
return "RSRC_STR(\"%s\")" % COLSTR(val, idaapi.SCOLOR_IMPNAME)
# -----------------------------------------------------------------------
# Table of formats and types to be registered/unregistered
# If a tuple has one element then it is the format to be registered with dtid=0
# If the tuple has more than one element, the tuple[0] is the data type and tuple[1:] are the data formats
new_formats = [
(pascal_data_type(), pascal_data_format()),
(simplevm_data_type(), simplevm_data_format()),
(makedword_data_format(),),
(simplevm_data_format(),)
]
if platform.system() == 'Windows':
new_formats.append((rsrc_string_format(),))
#</pycode(ex_custdata)>
# -----------------------------------------------------------------------
def nw_handler(code, old=0):
# delete notifications
if code == NW_OPENIDB:
idaapi.register_data_types_and_formats(new_formats)
elif code == NW_CLOSEIDB:
idaapi.unregister_data_types_and_formats(new_formats)
elif code == NW_TERMIDA:
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB | NW_REMOVE, nw_handler)
# -----------------------------------------------------------------------
# Check if already installed
if idaapi.find_custom_data_type(pascal_data_format.FORMAT_NAME) == -1:
if not idaapi.register_data_types_and_formats(new_formats):
print "Failed to register types!"
else:
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB, nw_handler)
print "Formats installed!"
else:
print "Formats already installed!"