-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfuncOutRecurse.py
More file actions
164 lines (130 loc) · 5.48 KB
/
Copy pathfuncOutRecurse.py
File metadata and controls
164 lines (130 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# IDAPython (IDA 9.x)
# -*- coding: utf-8 -*-
import idaapi
import idc
import idautils
import ida_funcs
import ida_xref
import ida_kernwin
import ida_name
try:
import ida_hexrays
except Exception:
ida_hexrays = None
def ensure_hexrays():
if ida_hexrays is None:
raise RuntimeError("Модуль ida_hexrays не найден. Нужен установленный Hex-Rays.")
if not ida_hexrays.init_hexrays_plugin():
raise RuntimeError("Hex-Rays не инициализировался. Проверьте лицензию/плагин.")
def get_func_name(ea: int) -> str:
n = ida_name.get_ea_name(ea)
if not n:
n = idc.get_func_name(ea)
return n or "sub_%X" % ea
def iter_direct_callees(func_ea: int):
"""
Ищет только прямые вызовы (call insn) внутри функции и возвращает адреса начала вызываемых функций.
"""
f = ida_funcs.get_func(func_ea)
if not f:
return
for ea in idautils.FuncItems(f.start_ea):
if idaapi.is_call_insn(ea):
# Берём только без-потоковые code refs (вызовы/прыжки)
for cref in idautils.CodeRefsFrom(ea, False):
callee = ida_funcs.get_func(cref)
if callee:
yield callee.start_ea
def collect_callees_dfs(start_ea: int):
"""
DFS-обход графа вызовов, начиная со start_ea.
Возвращает список функций (ea) в порядке первого обнаружения, стартовая функция исключена.
"""
visited = set([start_ea])
order = []
def visit(func_ea: int):
for callee_ea in iter_direct_callees(func_ea):
if callee_ea in visited:
continue
visited.add(callee_ea)
order.append(callee_ea)
visit(callee_ea)
visit(start_ea)
return order
def decompile_to_text(func_ea: int) -> str:
"""
Декомпилирует функцию в текст Hex-Rays. Возвращает чистый текст без цветовых тегов.
"""
try:
cfunc = ida_hexrays.decompile(func_ea)
except Exception as e:
raise RuntimeError(f"Не удалось декомпилировать {get_func_name(func_ea)} @ 0x{func_ea:X}: {e}")
# В новых IDA удобнее собирать через get_pseudocode()
lines = []
for pline in cfunc.get_pseudocode():
# line.line содержит строку с тегами; убираем цветовые теги
lines.append(idaapi.tag_remove(pline.line))
body = "\n".join(lines).rstrip()
header = f"/* === {get_func_name(func_ea)} (0x{func_ea:X}) === */"
return f"{header}\n{body}\n"
def ask_start_ea() -> int:
cur = ida_kernwin.get_screen_ea()
ea = ida_kernwin.ask_addr(cur, "Адрес начальной функции (EA):")
if ea is None:
raise RuntimeError("Адрес не задан.")
if not ida_funcs.get_func(ea):
raise RuntimeError("По указанному адресу функция не найдена.")
return ea
def ask_outfile() -> str:
path = ida_kernwin.ask_file(True, "*.c", "Сохранить псевдокод вызываемых функций в файл")
if not path:
raise RuntimeError("Файл не выбран.")
return path
def main(start_ea: int = None, out_path: str = None, include_root: bool = True):
"""
:param start_ea: адрес стартовой функции; если None — спросим у пользователя
:param out_path: путь к выходному файлу; если None — спросим у пользователя
:param include_root: включать ли в вывод саму стартовую функцию
"""
ensure_hexrays()
if start_ea is None:
start_ea = ask_start_ea()
if out_path is None:
out_path = ask_outfile()
if not ida_funcs.get_func(start_ea):
raise RuntimeError("Стартовая функция по указанному адресу отсутствует.")
order = collect_callees_dfs(start_ea)
chunks = []
skipped = []
if include_root:
try:
chunks.append(decompile_to_text(start_ea))
except Exception as e:
skipped.append(str(e))
for ea in order:
try:
chunks.append(decompile_to_text(ea))
except Exception as e:
skipped.append(str(e))
content = []
hdr = f"/* === Callee pseudocode dump ===\n" \
f" Root: {get_func_name(start_ea)} (0x{start_ea:X})\n" \
f" Count: {len(order)} function(s){' + root' if include_root else ''}\n" \
f" Generated by IDAPython\n" \
f"*/\n\n"
content.append(hdr)
content.extend(chunks)
if skipped:
content.append("\n/* === Не удалось декомпилировать ===\n")
for s in skipped:
content.append(" - " + s + "\n")
content.append("*/\n")
with open(out_path, "w", encoding="utf-8") as f:
f.write("".join(content))
ida_kernwin.info(f"Готово. В файл записано {len(chunks)} функций.\n{out_path}")
# Если запускать как скрипт из IDA:
if __name__ == "__main__":
try:
main()
except Exception as ex:
ida_kernwin.warning(f"[Ошибка] {ex}")