Skip to content

Commit fa4e016

Browse files
author
Igor Kirillov
committed
feat: Add rename locals, deep scan constructor/singleton
1) It's possible to transform v1 = v2 into v1 = _v1 or _v2 = v2 2) All variables apearing as return from some function can be deep scanned at once from that function 3) Remove some false-positive error messages when scanning
1 parent d3ce45b commit fa4e016

File tree

6 files changed

+164
-20
lines changed

6 files changed

+164
-20
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*.user
77
*.sln.docstates
88
*.coati*
9+
*.pyc
910

1011
# Build results
1112
[Dd]ebug/

HexRaysPyTools.py

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def hexrays_events_callback(*args):
2727
if Actions.RecastItemLeft.check(hx_view.cfunc, item):
2828
idaapi.attach_action_to_popup(form, popup, Actions.RecastItemLeft.name, None)
2929

30+
if Actions.RenameOther.check(hx_view.cfunc, item):
31+
idaapi.attach_action_to_popup(form, popup, Actions.RenameOther.name, None)
32+
3033
if Actions.RenameInside.check(hx_view.cfunc, item):
3134
idaapi.attach_action_to_popup(form, popup, Actions.RenameInside.name, None)
3235

@@ -43,6 +46,7 @@ def hexrays_events_callback(*args):
4346
if not hx_view.cfunc.entry_ea == idaapi.BADADDR: # Probably never happen
4447
idaapi.attach_action_to_popup(form, popup, Actions.AddRemoveReturn.name, None)
4548
idaapi.attach_action_to_popup(form, popup, Actions.ConvertToUsercall.name, None)
49+
idaapi.attach_action_to_popup(form, popup, Actions.DeepScanReturn.name, None)
4650

4751
elif item.citype == idaapi.VDI_LVAR:
4852
# If we clicked on argument
@@ -172,11 +176,13 @@ def init():
172176
Actions.register(Actions.ConvertToUsercall)
173177
Actions.register(Actions.ShallowScanVariable, Helper.temporary_structure)
174178
Actions.register(Actions.DeepScanVariable, Helper.temporary_structure)
179+
Actions.register(Actions.DeepScanReturn, Helper.temporary_structure)
175180
Actions.register(Actions.RecognizeShape)
176181
Actions.register(Actions.SelectContainingStructure, potential_negatives)
177182
Actions.register(Actions.ResetContainingStructure)
178183
Actions.register(Actions.RecastItemRight)
179184
Actions.register(Actions.RecastItemLeft)
185+
Actions.register(Actions.RenameOther)
180186
Actions.register(Actions.RenameInside)
181187
Actions.register(Actions.RenameOutside)
182188

@@ -209,11 +215,13 @@ def term():
209215
Actions.unregister(Actions.ConvertToUsercall)
210216
Actions.unregister(Actions.ShallowScanVariable)
211217
Actions.unregister(Actions.DeepScanVariable)
218+
Actions.unregister(Actions.DeepScanReturn)
212219
Actions.unregister(Actions.RecognizeShape)
213220
Actions.unregister(Actions.SelectContainingStructure)
214221
Actions.unregister(Actions.ResetContainingStructure)
215222
Actions.unregister(Actions.RecastItemRight)
216223
Actions.unregister(Actions.RecastItemLeft)
224+
Actions.unregister(Actions.RenameOther)
217225
Actions.unregister(Actions.RenameInside)
218226
Actions.unregister(Actions.RenameOutside)
219227
idaapi.term_hexrays_plugin()

HexRaysPyTools/Actions.py

+110-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
import re
44

55
import idaapi
6+
import idc
67

78
import HexRaysPyTools.Forms as Forms
89
import HexRaysPyTools.Core.Const as Const
910
import HexRaysPyTools.Core.Helper as Helper
1011
from HexRaysPyTools.Core.StructureGraph import StructureGraph
1112
from HexRaysPyTools.Core.TemporaryStructure import VirtualTable, TemporaryStructureModel
12-
from HexRaysPyTools.Core.VariableScanner import ShallowSearchVisitor, DeepSearchVisitor
13+
from HexRaysPyTools.Core.VariableScanner import ShallowSearchVisitor, DeepSearchVisitor, VariableLookupVisitor
1314
from HexRaysPyTools.Core.Helper import FunctionTouchVisitor
1415

1516
RECAST_LOCAL_VARIABLE = 0
@@ -83,7 +84,7 @@ def choose_til():
8384
if library_num != -1:
8485
selected_library = list_type_library[library_num][0]
8586
max_ordinal = idaapi.get_ordinal_qty(selected_library)
86-
if max_ordinal == idaapi.BADNODE:
87+
if max_ordinal == idaapi.BADORD:
8788
TypeLibrary.enable_library_ordinals(library_num - 1)
8889
max_ordinal = idaapi.get_ordinal_qty(selected_library)
8990
print "[DEBUG] Maximal ordinal of lib {0} = {1}".format(selected_library.name, max_ordinal)
@@ -95,7 +96,7 @@ def import_type(library, name):
9596
if library.name != idaapi.cvar.idati.name:
9697
last_ordinal = idaapi.get_ordinal_qty(idaapi.cvar.idati)
9798
type_id = idaapi.import_type(library, -1, name) # tid_t
98-
if type_id != idaapi.BADNODE:
99+
if type_id != idaapi.BADORD:
99100
return last_ordinal
100101
return None
101102

@@ -280,6 +281,7 @@ def activate(self, ctx):
280281
scanner.process()
281282
for field in scanner.candidates:
282283
self.temporary_structure.add_row(field)
284+
scanner.clear()
283285

284286
def update(self, ctx):
285287
if ctx.form_title[0:10] == "Pseudocode":
@@ -300,6 +302,9 @@ def __init__(self, temporary_structure):
300302
def activate(self, ctx):
301303
hx_view = idaapi.get_tform_vdui(ctx.form)
302304
variable = hx_view.item.get_lvar() # lvar_t
305+
self.scan(hx_view, variable)
306+
307+
def scan(self, hx_view, variable):
303308
if variable and filter(lambda x: x.equals_to(variable.type()), Const.LEGAL_TYPES):
304309
definition_address = variable.defea
305310
# index = list(hx_view.cfunc.get_lvars()).index(variable)
@@ -313,6 +318,61 @@ def activate(self, ctx):
313318
scanner.process()
314319
for field in scanner.candidates:
315320
self.temporary_structure.add_row(field)
321+
scanner.clear()
322+
323+
def update(self, ctx):
324+
if ctx.form_title[0:10] == "Pseudocode":
325+
return idaapi.AST_ENABLE_FOR_FORM
326+
return idaapi.AST_DISABLE_FOR_FORM
327+
328+
329+
class DeepScanReturn(idaapi.action_handler_t):
330+
331+
name = "my:DeepScanReturn"
332+
description = "Deep Scan Returned Variables"
333+
hotkey = None
334+
335+
def __init__(self, temporary_structure):
336+
self.temporary_structure = temporary_structure
337+
idaapi.action_handler_t.__init__(self)
338+
339+
@staticmethod
340+
def check(ctx):
341+
hx_view = idaapi.get_tform_vdui(ctx.form)
342+
return hx_view.cfunc.get_rettype().equals_to(Const.VOID_TINFO)
343+
344+
def activate(self, ctx):
345+
hx_view = idaapi.get_tform_vdui(ctx.form)
346+
address = hx_view.cfunc.entry_ea
347+
348+
xref_ea = idaapi.get_first_cref_to(address)
349+
xrefs = set()
350+
while xref_ea != idaapi.BADADDR:
351+
xref_func_ea = idc.GetFunctionAttr(xref_ea, idc.FUNCATTR_START)
352+
if xref_func_ea != idaapi.BADADDR:
353+
xrefs.add(xref_func_ea)
354+
else:
355+
print "[Warning] Function not found at 0x{0:08X}".format(xref_ea)
356+
xref_ea = idaapi.get_next_cref_to(address, xref_ea)
357+
358+
for func_ea in xrefs:
359+
visitor = VariableLookupVisitor(address)
360+
361+
try:
362+
cfunc = idaapi.decompile(func_ea)
363+
if cfunc:
364+
FunctionTouchVisitor(cfunc).process()
365+
visitor.apply_to(cfunc.body, None)
366+
for idx in visitor.result:
367+
scanner = DeepSearchVisitor(cfunc, 0, idx)
368+
scanner.process()
369+
for field in scanner.candidates:
370+
self.temporary_structure.add_row(field)
371+
372+
except idaapi.DecompilationFailure:
373+
print "[Warning] Failed to decompile function at 0x{0:08X}".format(xref_ea)
374+
375+
DeepSearchVisitor.clear()
316376

317377
def update(self, ctx):
318378
if ctx.form_title[0:10] == "Pseudocode":
@@ -693,6 +753,51 @@ def check(cfunc, ctree_item):
693753
return RECAST_RETURN, new_type, expression.x.x.obj_ea
694754

695755

756+
class RenameOther(idaapi.action_handler_t):
757+
name = "my:RenameOther"
758+
description = "Take other name"
759+
hotkey = "Ctrl+N"
760+
761+
def __init__(self):
762+
idaapi.action_handler_t.__init__(self)
763+
764+
@staticmethod
765+
def check(cfunc, ctree_item):
766+
if ctree_item.citype != idaapi.VDI_EXPR:
767+
return
768+
769+
expression = ctree_item.it.to_specific_type
770+
if expression.op != idaapi.cot_var:
771+
return
772+
773+
parent = cfunc.body.find_parent_of(expression).to_specific_type
774+
if parent.op != idaapi.cot_asg:
775+
return
776+
777+
other = parent.theother(expression)
778+
if other.op != idaapi.cot_var:
779+
return
780+
781+
this_lvar = ctree_item.get_lvar()
782+
other_lvar = cfunc.get_lvars()[other.v.idx]
783+
if (other_lvar.has_user_name or other_lvar.is_arg_var and re.search("a\d*$", other_lvar.name) is None) \
784+
and this_lvar.name.lstrip('_') != other_lvar.name.lstrip('_'):
785+
return '_' + other_lvar.name, this_lvar
786+
787+
def activate(self, ctx):
788+
hx_view = idaapi.get_tform_vdui(ctx.form)
789+
result = self.check(hx_view.cfunc, hx_view.item)
790+
791+
if result:
792+
name, lvar = result
793+
hx_view.rename_lvar(lvar, name, True)
794+
795+
def update(self, ctx):
796+
if ctx.form_title[0:10] == "Pseudocode":
797+
return idaapi.AST_ENABLE_FOR_FORM
798+
return idaapi.AST_DISABLE_FOR_FORM
799+
800+
696801
class RenameInside(idaapi.action_handler_t):
697802
name = "my:RenameInto"
698803
description = "Rename inside argument"
@@ -717,8 +822,8 @@ def check(cfunc, ctree_item):
717822
func_tinfo = parent.x.type.get_pointed_object()
718823
func_data = idaapi.func_type_data_t()
719824
func_tinfo.get_func_details(func_data)
720-
if arg_index < func_tinfo.get_nargs() and lvar.name != func_data[arg_index].name:
721-
return func_tinfo, parent.x.obj_ea, arg_index, lvar.name
825+
if arg_index < func_tinfo.get_nargs() and lvar.name.lstrip('_') != func_data[arg_index].name:
826+
return func_tinfo, parent.x.obj_ea, arg_index, lvar.name.lstrip('_')
722827

723828
def activate(self, ctx):
724829
hx_view = idaapi.get_tform_vdui(ctx.form)

HexRaysPyTools/Core/Helper.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,7 @@ def touch_all(self):
160160
try:
161161
cfunc = idaapi.decompile(address)
162162
if cfunc:
163-
touch_visitor = FunctionTouchVisitor(cfunc)
164-
touch_visitor.apply_to(cfunc.body, None)
165-
touch_visitor.touch_all()
163+
FunctionTouchVisitor(cfunc).process()
166164
except idaapi.DecompilationFailure:
167165
print "[ERROR] IDA failed to decompile function at 0x{address:08X}".format(address=address)
168166
idaapi.decompile(self.cfunc.entry_ea)

HexRaysPyTools/Core/TemporaryStructure.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ def name(self):
156156
name = idaapi.get_short_name(self.address)
157157
name = name.split('(')[0]
158158
result = re.search(r"(\[thunk\]:)?([^`]*)(.*\{(\d+)}.*)?", name)
159-
name, adjustor = result.group(2), result.group(4)
160-
if adjustor:
161-
name += "_adj_" + adjustor
159+
name, adjuster = result.group(2), result.group(4)
160+
if adjuster:
161+
name += "_adj_" + adjuster
162162
name = name.translate(None, "`'").replace(' ', '_')
163163
name = re.sub(r'[<>]', '_t_', name)
164164
return name

HexRaysPyTools/Core/VariableScanner.py

+41-9
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def check_member_assignment(self, expression, index):
115115
# Assignment like (v1 = v2) where v2 is scanned variable
116116
if parents[0].x.op == idaapi.cot_var:
117117
self.add_variable(parents[0].x.v.idx)
118+
return
118119
else:
119120
# if expression is (var = something), we have to explore whether continue to scan this variable or not
120121
if parents[0].y.op != idaapi.cot_num:
@@ -139,7 +140,7 @@ def check_member_assignment(self, expression, index):
139140
index, self.expression_address
140141
)
141142
self.variables.pop(index)
142-
return
143+
return
143144

144145
# Assignment like v1 = (TYPE) v2 where TYPE is one the supported types
145146
elif parents_type[0:3] == ['cast', 'asg', 'expr']:
@@ -166,14 +167,15 @@ def check_member_assignment(self, expression, index):
166167
offset = parents[0].theother(expression).numval()
167168

168169
if parents_type[2] == 'ptr':
169-
if parents_type[3] == 'asg' and parents[3].x == parents[2]:
170-
# *(TYPE *)(var + x) = ???
171-
return self.get_member(
172-
offset, index, object=parents[3].y, default=parents[1].type.get_pointed_object()
173-
)
174-
# other_var = *(TYPE *)(var + x)
175-
if parents[3].x.op == idaapi.cot_var:
176-
return self.create_member(offset, index, parents[3].x.type)
170+
if parents_type[3] == 'asg':
171+
if parents[3].x == parents[2]:
172+
# *(TYPE *)(var + x) = ???
173+
return self.get_member(
174+
offset, index, object=parents[3].y, default=parents[1].type.get_pointed_object()
175+
)
176+
if parents[3].x.op == idaapi.cot_var:
177+
# other_var = *(TYPE *)(var + x)
178+
return self.create_member(offset, index, parents[3].x.type)
177179
return self.create_member(offset, index, parents[1].type.get_pointed_object())
178180

179181
elif parents_type[2] == 'call':
@@ -217,6 +219,7 @@ def check_member_assignment(self, expression, index):
217219
elif parents_type[1] == 'asg':
218220
if parents[1].y == parents[0] and parents[1].x.op == idaapi.cot_var:
219221
self.scan_function(self.function.entry_ea, offset, parents[1].x.v.idx)
222+
return
220223

221224
elif parents_type[0] == 'asg':
222225
# var = (int)&Some_object
@@ -324,6 +327,9 @@ def check_member_assignment(self, expression, index):
324327
# *var
325328
return self.create_member(0, index, self.variables[index].get_pointed_object())
326329

330+
elif parents_type[0] == 'asg':
331+
return
332+
327333
if 'return' not in parents_type[0:2] and parents_type[0] not in ('if', 'band', 'eq', 'ne', 'cast'):
328334
print "[DEBUG] Unhandled type", self.variables[index].dstr(), \
329335
"Index:", index, \
@@ -338,6 +344,9 @@ def process(self):
338344
don't wind up in infinite recursion.
339345
"""
340346
self.apply_to(self.function.body, None)
347+
348+
@staticmethod
349+
def clear():
341350
scanned_functions.clear()
342351

343352

@@ -362,3 +371,26 @@ def scan_function(self, ea, offset, arg_index):
362371
self.candidates.extend(scanner.candidates)
363372
except idaapi.DecompilationFailure:
364373
print "[ERROR] Ida failed to decompile function"
374+
375+
376+
class VariableLookupVisitor(idaapi.ctree_parentee_t):
377+
""" Helps to find all variables that are returned by some function placed at func_address """
378+
379+
def __init__(self, func_address):
380+
super(VariableLookupVisitor, self).__init__()
381+
self.func_address = func_address
382+
self.result = []
383+
384+
def visit_expr(self, expression):
385+
# We are looking for expressions like `var = func(...)` or `var = (TYPE) func(...)`
386+
if expression.op == idaapi.cot_asg and expression.x.op == idaapi.cot_var:
387+
if expression.y.op == idaapi.cot_call:
388+
if self.__check_call(expression.y) or \
389+
expression.y.op == idaapi.cot_cast and expression.y.x.op == idaapi.cot_call:
390+
391+
idx = expression.x.v.idx
392+
self.result.append(idx)
393+
return 0
394+
395+
def __check_call(self, expression):
396+
return expression.x.obj_ea == self.func_address

0 commit comments

Comments
 (0)