mirror of
https://github.com/cemu-project/idapython.git
synced 2024-11-24 10:09:20 +01:00
217 lines
7.9 KiB
Python
217 lines
7.9 KiB
Python
|
# -----------------------------------------------------------------------
|
||
|
# 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!"
|