Skip to content

Commit 6b0dfd8

Browse files
author
elias.bachaalany
committed
IDAPython 1.4.1:
- added AUTHORS.txt and changed the banner - IDAPython_ExecFile() will print script execution errors to the log window too - added 'ph' into idaapi. It is a replacement for idaapi.cvar.ph - added runscript to init.py for backward compatibility - added cli_t support
1 parent 8495e52 commit 6b0dfd8

10 files changed

Lines changed: 959 additions & 25 deletions

File tree

AUTHORS.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
The IDAPython Team:
2+
3+
* Gergely Erdelyi - http://d-dome.net/idapython/
4+
5+
Original IDAPython author.
6+
7+
* Hex-Rays - http://www.hex-rays.com/
8+
9+
Hex-Rays joined the project in September 2009 and started contributing.
10+
11+
* Ero Carrera - http://dkbza.org/
12+
13+
Project contributor

build.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
# IDAPython version
3737
VERSION_MAJOR = 1
3838
VERSION_MINOR = 4
39-
VERSION_PATCH = 0
39+
VERSION_PATCH = 1
4040

4141
# Determine Python version
4242
PYTHON_MAJOR_VERSION = int(platform.python_version()[0])
@@ -66,12 +66,15 @@
6666
"README.txt",
6767
"COPYING.txt",
6868
"CHANGES.txt",
69+
"AUTHORS.txt",
6970
"STATUS.txt",
7071
"docs/notes.txt",
7172
"examples/chooser.py",
7273
"examples/colours.py",
7374
"examples/debughook.py",
75+
"examples/ex_cli.py",
7476
"examples/ex1.idc",
77+
"examples/ex_custdata.py",
7578
"examples/ex1_idaapi.py",
7679
"examples/ex1_idautils.py",
7780
"examples/hotkey.py",

examples/ex_cli.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# -----------------------------------------------------------------------
2+
# This is an example illustrating how to implement a CLI
3+
# (c) Hex-Rays
4+
#
5+
from idaapi import NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE, COLSTR, cli_t
6+
7+
#<pycode(ex_cli_ex1)>
8+
class mycli_t(cli_t):
9+
flags = 0
10+
sname = "pycli"
11+
lname = "Python CLI"
12+
hint = "pycli hint"
13+
14+
def OnExecuteLine(self, line):
15+
"""
16+
The user pressed Enter. The CLI is free to execute the line immediately or ask for more lines.
17+
18+
This callback is mandatory.
19+
20+
@param line: typed line(s)
21+
@return Boolean: True-executed line, False-ask for more lines
22+
"""
23+
print "OnExecute:", line
24+
return True
25+
26+
def OnKeydown(self, line, x, sellen, vkey, shift):
27+
"""
28+
A keyboard key has been pressed
29+
This is a generic callback and the CLI is free to do whatever it wants.
30+
31+
This callback is optional.
32+
33+
@param line: current input line
34+
@param x: current x coordinate of the cursor
35+
@param sellen: current selection length (usually 0)
36+
@param vkey: virtual key code. if the key has been handled, it should be returned as zero
37+
@param shift: shift state
38+
39+
@return:
40+
None - Nothing was changed
41+
tuple(line, x, sellen, vkey): if either of the input line or the x coordinate or the selection length has been modified.
42+
It is possible to return a tuple with None elements to preserve old values. Example: tuple(new_line, None, None, None) or tuple(new_line)
43+
"""
44+
print "Onkeydown: line=%s x=%d sellen=%d vkey=%d shift=%d" % (line, x, sellen, vkey, shift)
45+
return None
46+
47+
def OnCompleteLine(self, prefix, n, line, prefix_start):
48+
"""
49+
The user pressed Tab. Find a completion number N for prefix PREFIX
50+
51+
This callback is optional.
52+
53+
@param prefix: Line prefix at x (string)
54+
@param n: completion number (int)
55+
@param line: the current line (string)
56+
@param prefix_start: the index where PREFIX starts in LINE (int)
57+
58+
@return: None if no completion could be generated otherwise a String with the completion suggestion
59+
"""
60+
print "OnCompleteLine: prefix=%s n=%d line=%s prefix_start=%d" % (prefix, n, line, prefix_start)
61+
return None
62+
#</pycode(ex_cli_ex1)>
63+
64+
65+
# -----------------------------------------------------------------------
66+
def nw_handler(code, old=0):
67+
if code == NW_OPENIDB:
68+
print "nw_handler(): installing CLI"
69+
mycli.register()
70+
elif code == NW_CLOSEIDB:
71+
print "nw_handler(): removing CLI"
72+
mycli.unregister()
73+
elif code == NW_TERMIDA:
74+
print "nw_handler(): uninstalled nw handler"
75+
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB | NW_REMOVE, nw_handler)
76+
77+
# -----------------------------------------------------------------------
78+
79+
# Already installed?
80+
try:
81+
mycli
82+
# remove previous CLI
83+
mycli.unregister()
84+
del mycli
85+
# remove previous handler
86+
nw_handler(NW_TERMIDA)
87+
except:
88+
pass
89+
finally:
90+
mycli = mycli_t()
91+
92+
# register CLI
93+
if mycli.register():
94+
print "CLI installed"
95+
# install new handler
96+
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB, nw_handler)
97+
else:
98+
del mycli
99+
print "Failed to install CLI"
100+

examples/ex_custdata.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# -----------------------------------------------------------------------
2+
# This is an example illustrating how to use custom data types in Python
3+
# (c) Hex-Rays
4+
#
5+
from idaapi import data_type_t, data_format_t, NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE, COLSTR
6+
import struct
7+
import ctypes
8+
import platform
9+
10+
#<pycode(ex_custdata)>
11+
12+
# -----------------------------------------------------------------------
13+
class pascal_data_type(data_type_t):
14+
def __init__(self):
15+
data_type_t.__init__(self, name="py_pascal_string",
16+
value_size = 2, menu_name = "Pascal string",
17+
asm_keyword = "pstr")
18+
19+
def calc_item_size(self, ea, maxsize):
20+
# Custom data types may be used in structure definitions. If this case
21+
# ea is a member id. Check for this situation and return 1
22+
if _idaapi.is_member_id(ea):
23+
return 1
24+
25+
# get the length byte
26+
n = _idaapi.get_byte(ea)
27+
28+
# string too big?
29+
if n > maxsize:
30+
return 0
31+
# ok, accept the string
32+
return n + 1
33+
34+
class pascal_data_format(data_format_t):
35+
FORMAT_NAME = "py_pascal_string_pstr"
36+
def __init__(self):
37+
data_format_t.__init__(self, name=pascal_data_format.FORMAT_NAME)
38+
39+
def printf(self, value, current_ea, operand_num, dtid):
40+
# Take the length byte
41+
n = ord(value[0])
42+
o = ['"']
43+
for ch in value[1:]:
44+
b = ord(ch)
45+
if b < 0x20 or b > 128:
46+
o.append(r'\x%02x' % ord(ch))
47+
else:
48+
o.append(ch)
49+
o.append('"')
50+
return "".join(o)
51+
52+
# -----------------------------------------------------------------------
53+
class simplevm_data_type(data_type_t):
54+
ASM_KEYWORD = "svm_emit"
55+
def __init__(self):
56+
data_type_t.__init__(self,
57+
name="py_simple_vm",
58+
value_size = 1,
59+
menu_name = "SimpleVM",
60+
asm_keyword = simplevm_data_type.ASM_KEYWORD)
61+
62+
def calc_item_size(self, ea, maxsize):
63+
if _idaapi.is_member_id(ea):
64+
return 1
65+
# get the opcode and see if it has an imm
66+
n = 5 if (_idaapi.get_byte(ea) & 3) == 0 else 1
67+
# string too big?
68+
if n > maxsize:
69+
return 0
70+
# ok, accept
71+
return n
72+
73+
class simplevm_data_format(data_format_t):
74+
def __init__(self):
75+
data_format_t.__init__(self,
76+
name="py_simple_vm_format",
77+
menu_name = "SimpleVM")
78+
79+
# Some tables for the disassembler
80+
INST = {1: 'add', 2: 'mul', 3: 'sub', 4: 'xor', 5: 'mov'}
81+
REGS = {1: 'r1', 2: 'r2', 3: 'r3'}
82+
def disasm(self, inst):
83+
"""A simple local disassembler. In reality one can use a full-blown disassembler to render the text"""
84+
opbyte = ord(inst[0])
85+
op = opbyte >> 4
86+
if not (1<=op<=5):
87+
return None
88+
r1 = (opbyte & 0xf) >> 2
89+
r2 = opbyte & 3
90+
sz = 0
91+
if r2 == 0:
92+
if len(inst) != 5:
93+
return None
94+
imm = struct.unpack_from('L', inst, 1)[0]
95+
sz = 5
96+
else:
97+
imm = None
98+
sz = 1
99+
text = "%s %s, %s" % (
100+
COLSTR(simplevm_data_format.INST[op], idaapi.SCOLOR_INSN),
101+
COLSTR(simplevm_data_format.REGS[r1], idaapi.SCOLOR_REG),
102+
COLSTR("0x%08X" % imm, idaapi.SCOLOR_NUMBER) if imm is not None else COLSTR(simplevm_data_format.REGS[r2], idaapi.SCOLOR_REG))
103+
return (sz, text)
104+
105+
def printf(self, value, current_ea, operand_num, dtid):
106+
r = self.disasm(value)
107+
if not r:
108+
return None
109+
if dtid == 0:
110+
return "%s(%s)" % (simplevm_data_type.ASM_KEYWORD, r[1])
111+
return r[1]
112+
113+
# -----------------------------------------------------------------------
114+
# This format will display DWORD values as MAKE_DWORD(0xHI, 0xLO)
115+
class makedword_data_format(data_format_t):
116+
def __init__(self):
117+
data_format_t.__init__(self,
118+
name="py_makedword",
119+
value_size = 4,
120+
menu_name = "Make DWORD")
121+
122+
def printf(self, value, current_ea, operand_num, dtid):
123+
if len(value) != 4: return None
124+
w1 = struct.unpack_from("H", value, 0)[0]
125+
w2 = struct.unpack_from("H", value, 2)[0]
126+
return "MAKE_DWORD(0x%04X, 0x%04X)" % (w2, w1)
127+
128+
# -----------------------------------------------------------------------
129+
# This format will try to load a resource string given a number
130+
# So instead of displaying:
131+
# push 66h
132+
# call message_box_from_rsrc_string
133+
# It can be rendered as;
134+
# push RSRC("The message")
135+
# call message_box_from_rsrc_string
136+
#
137+
# The get_rsrc_string() is not optimal since it loads/unloads the
138+
# DLL each time for a new string. It can be improved in many ways.
139+
class rsrc_string_format(data_format_t):
140+
def __init__(self):
141+
data_format_t.__init__(self,
142+
name="py_w32rsrcstring",
143+
value_size = 1,
144+
menu_name = "Resource string")
145+
self.cache_node = idaapi.netnode("$ py_w32rsrcstring", 0, 1)
146+
147+
def get_rsrc_string(self, fn, id):
148+
"""
149+
Simple method that loads the input file as a DLL with LOAD_LIBRARY_AS_DATAFILE flag.
150+
It then tries to LoadString()
151+
"""
152+
k32 = ctypes.windll.kernel32
153+
u32 = ctypes.windll.user32
154+
155+
hinst = k32.LoadLibraryExA(fn, 0, 0x2)
156+
if hinst == 0:
157+
return ""
158+
buf = ctypes.create_string_buffer(1024)
159+
r = u32.LoadStringA(hinst, id, buf, 1024-1)
160+
k32.FreeLibrary(hinst)
161+
return buf.value if r else ""
162+
163+
def printf(self, value, current_ea, operand_num, dtid):
164+
# Is it already cached?
165+
val = self.cache_node.supval(current_ea)
166+
167+
# Not cached?
168+
if val == None:
169+
# Retrieve it
170+
num = idaapi.struct_unpack(value)
171+
val = self.get_rsrc_string(idaapi.get_input_file_path(), num)
172+
# Cache it
173+
self.cache_node.supset(current_ea, val)
174+
175+
# Failed to retrieve?
176+
if val == "" or val == "\x00":
177+
return None
178+
# Return the format
179+
return "RSRC_STR(\"%s\")" % COLSTR(val, idaapi.SCOLOR_IMPNAME)
180+
181+
# -----------------------------------------------------------------------
182+
# Table of formats and types to be registered/unregistered
183+
# If a tuple has one element then it is the format to be registered with dtid=0
184+
# If the tuple has more than one element, the tuple[0] is the data type and tuple[1:] are the data formats
185+
new_formats = [
186+
(pascal_data_type(), pascal_data_format()),
187+
(simplevm_data_type(), simplevm_data_format()),
188+
(makedword_data_format(),),
189+
(simplevm_data_format(),)
190+
]
191+
192+
if platform.system() == 'Windows':
193+
new_formats.append((rsrc_string_format(),))
194+
195+
#</pycode(ex_custdata)>
196+
197+
# -----------------------------------------------------------------------
198+
def nw_handler(code, old=0):
199+
# delete notifications
200+
if code == NW_OPENIDB:
201+
idaapi.register_data_types_and_formats(new_formats)
202+
elif code == NW_CLOSEIDB:
203+
idaapi.unregister_data_types_and_formats(new_formats)
204+
elif code == NW_TERMIDA:
205+
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB | NW_REMOVE, nw_handler)
206+
207+
# -----------------------------------------------------------------------
208+
# Check if already installed
209+
if idaapi.find_custom_data_type(pascal_data_format.FORMAT_NAME) == -1:
210+
if not idaapi.register_data_types_and_formats(new_formats):
211+
print "Failed to register types!"
212+
else:
213+
idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB, nw_handler)
214+
print "Formats installed!"
215+
else:
216+
print "Formats already installed!"

python/init.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,27 @@ def flush(self):
3939
def isatty(self):
4040
return False
4141

42+
# -----------------------------------------------------------------------
43+
def runscript(script):
44+
"""
45+
Executes a script.
46+
This function is present for backward compatiblity. Please use idaapi.IDAPython_ExecScript() instead
47+
48+
@param script: script path
49+
50+
@return: Error string or None on success
51+
"""
52+
53+
import idaapi
54+
return idaapi.IDAPython_ExecScript(script, globals())
55+
4256
# -----------------------------------------------------------------------
4357
def print_banner():
4458
banner = [
45-
"Python interpreter version %d.%d.%d %s (serial %d)" % sys.version_info,
46-
"Copyright (c) 1990-2010 Python Software Foundation - http://www.python.org/",
47-
"",
48-
"IDAPython" + (" 64-bit" if __EA64__ else "") + " version %d.%d.%d %s (serial %d)" % IDAPYTHON_VERSION,
49-
"Copyright (c) 2004-2010 Gergely Erdelyi - http://code.google.com/p/idapython/"
59+
"Python %d.%d.%d %s (serial %d) (c) 1990-2010 Python Software Foundation" % sys.version_info,
60+
"IDAPython" + (" 64-bit" if __EA64__ else "") + " v%d.%d.%d %s (serial %d) (c) The IDAPython Team <[email protected]>" % IDAPYTHON_VERSION
5061
]
51-
sepline = '-' * max([len(s) for s in banner])
62+
sepline = '-' * (max([len(s) for s in banner])+1)
5263

5364
print sepline
5465
print "\n".join(banner)

0 commit comments

Comments
 (0)