-
Notifications
You must be signed in to change notification settings - Fork 11
/
symless_plugin.py
168 lines (132 loc) · 5.05 KB
/
symless_plugin.py
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
165
166
167
168
import base64
import collections
import importlib
import inspect
import os
import pkgutil
import sys
from typing import Collection
import idaapi
import symless
import symless.plugins as plugins
import symless.utils.utils as utils
class fixedBtn(idaapi.Form.ButtonInput):
def __init__(self, plugin: "SymlessPlugin"):
super().__init__(self.reload, "0")
self.plugin = plugin
def reload(self, code):
idaapi.show_wait_box("Reloading Symless..")
try:
# terminate all extensions
self.plugin.term()
# reload symless code
reload_plugin()
# rebind all extensions
self.plugin.find_extensions()
except Exception as e:
import traceback
utils.g_logger.critical(repr(e) + "\n" + traceback.format_exc())
finally:
idaapi.hide_wait_box()
def get_tag(self):
return "<Reload:%s%d:%s%s:%s:%s>" % (
self.tp,
self.id,
"+" if self.is_relative_offset else "",
self.width,
self.swidth,
":" if self.hlp is None else self.hlp,
)
class SymlessInfoForm(idaapi.Form):
def __init__(self, plugin: "SymlessPlugin"):
icon_path = os.path.join(os.path.abspath(symless.__path__[0]), "resources", "bigger_champi.png")
with open(icon_path, "rb") as file:
icon_b64 = base64.b64encode(file.read()).decode()
img_html = "<img src='data:image/png;base64,%s'>" % icon_b64
info_html = """
<div style='text-align: center; margin: 6px; font-size: 16px;'>
<pre style='font-size: 72px;'>Symless</pre>
<pre style='font-size: 16px; text-align: left;'>%s<br><br>Version: <b>%.1f</b></pre>
</div>
""" % (
symless.PLUGIN_DESC,
symless.PLUGIN_VERSION,
)
super().__init__(
"BUTTON YES NONE\nBUTTON CANCEL NONE\nSymless plugin\n{img}<|>{info}\n{reload}",
{
"img": idaapi.Form.StringLabel(img_html, tp=idaapi.Form.FT_HTML_LABEL, size=None),
"info": idaapi.Form.StringLabel(info_html, tp=idaapi.Form.FT_HTML_LABEL, size=None),
"reload": fixedBtn(plugin),
},
)
# Symless plugin
class SymlessPlugin(idaapi.plugin_t):
flags = idaapi.PLUGIN_MOD | idaapi.PLUGIN_PROC # | idaapi.PLUGIN_HIDE
comment = "Symless interactive plugin"
wanted_name = "Symless"
help = "" # not used, IDA < 7.6 compatibility
wanted_hotkey = "" # not used, IDA < 7.6 compatibility
# find & initialize all extensions
def init(self) -> idaapi.plugmod_t:
self.ext: Collection[plugins.plugin_t] = collections.deque()
self.find_extensions()
return idaapi.PLUGIN_KEEP
# find and load extensions from symless plugins folder
def find_extensions(self):
for mod_info in pkgutil.walk_packages(plugins.__path__, prefix="symless.plugins."):
if mod_info.ispkg:
continue
spec = mod_info.module_finder.find_spec(mod_info.name)
module = importlib.util.module_from_spec(spec)
# module is already loaded
if module.__name__ in sys.modules:
module = sys.modules[module.__name__]
# load the module
else:
sys.modules[module.__name__] = module
try:
spec.loader.exec_module(module)
except BaseException as e:
sys.modules.pop(module.__name__)
print(f"Error while loading extension {mod_info.name}: {e}")
continue
# module defines an extension
if not hasattr(module, "get_plugin"):
continue
ext: plugins.plugin_t = module.get_plugin()
self.ext.append(ext)
# debug - reload plugin action
def run(self, args):
info = SymlessInfoForm(self)
info.Compile()
return info.Execute()
# term all extensions
def term(self):
while len(self.ext) > 0:
ext = self.ext.pop()
ext.term()
def PLUGIN_ENTRY() -> idaapi.plugin_t:
return SymlessPlugin()
# reload one module, by first reloading all imports from that module
# to_reload contains all modules to reload
def reload_module(module, to_reload: set):
if module not in to_reload:
return
# remove from set first, avoid infinite recursion if recursive imports
to_reload.remove(module)
# reload all imports first
for _, dep in inspect.getmembers(module, lambda k: inspect.ismodule(k)):
reload_module(dep, to_reload)
# reload the module
utils.g_logger.info(f"Reloading {module.__name__} ..")
importlib.reload(module)
# reload all symless code
def reload_plugin():
# list all modules to reload, unordered
to_reload = set()
for k, mod in sys.modules.items():
if k.startswith("symless"):
to_reload.add(mod)
for mod in list(to_reload): # copy to alter
reload_module(mod, to_reload)