├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── ida_fuzzy.py
└── screenshots
├── idafuzzy.gif
├── idafuzzy.png
├── jumpf.gif
└── structdef.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | #IDEA
107 | .idea/
108 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## -> 1.0.0
4 |
5 | Initial release
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Ga_ryo_
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
🔎 IDAFuzzy 🔍
2 | Fuzzy searching tool for IDA Pro.
3 |
4 | ## What's IDAFuzzy?
5 | IDAFuzzy is fuzzy searching tool for IDA Pro.
6 | This tool helps you to find command/function/struct and so on.
7 | This tool is usefull when
8 | 1. You don't remember all shortcut.
9 | 2. You don't remember all function/struct name exactly.
10 |
11 | This tool is inspired by Mac's Spotlight and Intellij's Search Everywhere dialog.
12 |
13 | (Only IDA Pro 7 is tested.)
14 |
15 | ## Requirements
16 | It requires fuzzywuzzy.
17 | ```
18 | pip install fuzzywuzzy[speedup]
19 | ```
20 |
21 | ## Installation
22 | Put ```ida_fuzzy.py``` into ```plugins``` directory.
23 |
24 | ## Usage
25 | Just do as follows.
26 |
27 | 1. Type SHIFT+SPACE.
28 | 2. Type as you like. (e.g. "snap da")
29 | 3. Type TAB for selecting.(First one is automatically selected.)
30 | 4. Type Enter to (execute command/jump to address/jump to struct definition).
31 |
32 | 
33 |
34 |
35 | ### Jump to function
36 | ---
37 | 
38 |
39 | ### Jump to struct definition
40 | ---
41 | 
42 |
--------------------------------------------------------------------------------
/ida_fuzzy.py:
--------------------------------------------------------------------------------
1 | from idaapi import Form
2 | from idaapi import Choose
3 | import idaapi
4 | from ida_kernwin import *
5 | from fuzzywuzzy import process, fuzz
6 | from idautils import *
7 | import gc
8 | import functools
9 | from PyQt5 import QtCore
10 | import threading
11 |
12 | """
13 | Fuzzy Search v1.0
14 | Goal
15 | 1. Search and execute IDA Pro's feature by name(ex: file,next code, run, attach to process ... )
16 | 2. Search and goto Function, string, struct,...
17 | 3. Automatically update. (when user rename function, hook and refresh)
18 |
19 | Choose.CH_QFTYP_FUZZY is not so usable.
20 | 1. Not so fuzzy.
21 | 2. In the first place, fuzzy choose isn't applied to Functions Window or other embedded chooser.
22 |
23 | @TODO
24 | 1. Installation
25 | - install idapython
26 | - pip install fuzzywuzzy
27 | - put this file to plugins directory.
28 |
29 | 2. Usage
30 | 3. Implement
31 | - All feature
32 | - Functions (hook rename and reload automatically)
33 | - Strings (symbol and Contents)
34 | - Structures
35 | - etc...
36 |
37 | 4. Show hint?
38 | - Name = "strings windows", Hint = "Open strings subview in current context."
39 | -- but add column affects number of pushing tab.
40 | """
41 |
42 | LISTLEN = 10
43 |
44 | class Commands(object):
45 | """
46 | Command execution proxy.
47 | """
48 |
49 | def __init__(self, **kwargs):
50 | self.kwargs = kwargs
51 | assert (callable(kwargs['fptr']))
52 | # assert(kwargs.get('description') != None)
53 |
54 | @property
55 | def description(self):
56 | return self.kwargs.get('description')
57 |
58 | def execute(self):
59 | # ea = get_screen_ea()
60 | # open_strings_window(ea)
61 | if self.kwargs.get('args') is not None:
62 | self.kwargs.get('fptr')(*self.kwargs.get('args'))
63 | else:
64 | self.kwargs.get('fptr')()
65 |
66 | def get_icon(self):
67 | if self.kwargs.get('icon') is None:
68 | return 0
69 | return self.kwargs.get('icon')
70 |
71 |
72 | choices = {}
73 | names = []
74 |
75 |
76 | class EmbeddedChooserClass(Choose):
77 | """
78 | A simple chooser to be used as an embedded chooser
79 | """
80 |
81 | def __init__(self, title, nb=5, flags=0):
82 | Choose.__init__(self,
83 | title,
84 | [["Action", 30 | Choose.CHCOL_PLAIN]],
85 | embedded=True, height=10, flags=flags)
86 | # embedded=True, width=30, height=20, flags=flags)
87 |
88 | self.n = 0
89 | self.items = []
90 | self.icon = 0
91 |
92 | def OnGetIcon(self, n):
93 | # print("get icon %d" % n)
94 | return choices[self.items[n][0]].get_icon()
95 |
96 | def OnSelectionChange(self, n):
97 | pass
98 | # print("selection change %d" % n)
99 |
100 | def OnClose(self):
101 | pass
102 |
103 | def OnGetLine(self, n):
104 | # print("getline %d" % n)
105 | return self.items[n]
106 |
107 | def OnGetSize(self):
108 | n = len(self.items)
109 | # print("getsize -> %d" % n)
110 | return n
111 |
112 | """
113 | def hooked_scorer(*args,**kwargs):
114 | # watch event every time.
115 | pass
116 |
117 | def hook(function, prefunction):
118 | @functools.wraps(function)
119 | def run(*args, **kwargs):
120 | prefunction(*args, **kwargs)
121 | return function(*args, **kwargs)
122 | return run
123 | fuzz.WRatio = hook(fuzz.WRatio, hooked_scorer)
124 |
125 | """
126 |
127 | class TerminateException(Exception):
128 | pass
129 |
130 | def hooked_scorer(*args, **kwargs):
131 | if kwargs.pop('terminate_event').is_set():
132 | raise TerminateException
133 | return fuzz.WRatio(*args, **kwargs)
134 |
135 | class FuzzySearchThread(QtCore.QThread):
136 | refresh_list = QtCore.pyqtSignal([str]*LISTLEN)
137 |
138 | def __init__(self, parent=None):
139 | super(FuzzySearchThread, self).__init__(parent)
140 | self.stopped = False
141 | self.mutex = QtCore.QMutex()
142 | self.terminate_event = threading.Event()
143 |
144 | def setup(self, s):
145 | self.stoppped = False
146 | self.s = s
147 |
148 | def stop( self ):
149 | with QtCore.QMutexLocker( self.mutex ):
150 | self.stopped = True
151 |
152 | def run(self):
153 | f = functools.partial(hooked_scorer, terminate_event=self.terminate_event)
154 | try:
155 | res = process.extract(self.s, names, limit=LISTLEN, scorer=f) # f.iStr1.value won't change until Form.Execute() returns.
156 | extracts = []
157 | for i in res:
158 | extracts.append(i[0])
159 | for i in range(10-len(res)):
160 | extracts.append("")
161 | self.refresh_list.emit(*extracts) # call main Thread's UI function.
162 | except TerminateException:
163 | pass
164 | self.stop()
165 | self.finished.emit()
166 |
167 |
168 | # --------------------------------------------------------------------------
169 | class FuzzySearchForm(Form):
170 | def __init__(self):
171 | self.invert = False
172 | self.EChooser = EmbeddedChooserClass("Title", flags=Choose.CH_MODAL)
173 | self.selected_id = 0
174 | self.s = ""
175 | self.fst = FuzzySearchThread()
176 | self.fst.refresh_list.connect(self.refresh_list)
177 | self.fst.finished.connect(self.finished)
178 | # self.EChooser = EmbeddedChooserClass("Title", flags=Choose.CH_CAN_REFRESH)
179 |
180 | # Portability fix from Python2 to Python3.
181 | try:
182 | self.cEChooser = super().cEChooser #super() will raise exception in python2
183 | except:
184 | pass
185 |
186 | Form.__init__(self, r"""STARTITEM
187 | IDA Fuzzy Search
188 | {FormChangeCb}
189 | <:{iStr1}>
190 |
191 |
192 | """, {
193 | 'iStr1': Form.StringInput(),
194 | 'cEChooser': Form.EmbeddedChooserControl(self.EChooser),
195 | 'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
196 | })
197 | # self.modal = False
198 |
199 | def OnFormChange(self, fid):
200 | if fid == -1:
201 | # initialize
202 | pass
203 | elif fid == -2:
204 | # terminate
205 | pass
206 | elif fid == self.cEChooser.id:
207 | self.selected_id = self.GetControlValue(self.cEChooser)[0]
208 | elif fid == self.iStr1.id:
209 | self.s = self.GetControlValue(self.iStr1)
210 | self.EChooser.items = []
211 | if self.s == '':
212 | self.RefreshField(self.cEChooser)
213 | return 1
214 | self.fst.stop()
215 | self.fst.quit() # if you type speedy, FuzzySearch which executed before is not finished here.
216 | self.fst.terminate_event.set()
217 | self.fst.wait()
218 | #self.fst.terminate() # but last time's FuzzySearch is meaningless, so terminate this. <- little dangerous?
219 |
220 | #stop and quit take time.(and maybe non-blocking)
221 | #So if you type speedy, some start() call will be ignored.
222 | #re-create thread solve this.
223 | self.fst = FuzzySearchThread()
224 | self.fst.refresh_list.connect(self.refresh_list)
225 | self.fst.finished.connect(self.finished)
226 | self.fst.setup(self.s)
227 | self.fst.start()
228 |
229 | # extracts = process.extract(s, names, limit=10) # f.iStr1.value won't change until Form.Execute() returns.
230 | else:
231 | pass
232 | return 1
233 |
234 | def refresh_list(self, *extracts):
235 | for ex in extracts:
236 | # self.EChooser.items.append([ex[0], choices[ex[0]].description])
237 | self.EChooser.items.append([ex])
238 | self.RefreshField(self.cEChooser)
239 | self.SetControlValue(self.cEChooser, [0]) # set cursor top
240 |
241 | def finished(self):
242 | pass
243 |
244 | def get_selected_item(self):
245 | if self.selected_id == -1:
246 | return None
247 | item_name = self.EChooser.items[self.selected_id][0]
248 | return choices[item_name]
249 |
250 |
251 | # --------------------------------------------------------------------------
252 | def fuzzy_search_main():
253 | # Create form
254 | global f, choices, names
255 | choices = {}
256 | names = []
257 | gc.collect()
258 |
259 | # Runtime collector.
260 | # Pros
261 | # 1. No need to refresh automatically.(When GDB start, libc symbol,PIE's symbol,etc... address will change.When user rename symbol, also.)
262 | # 1.1. If you want to search library's function, view module list and right-click onto target library. Then click "Analyze module".
263 | # 2. Action's state is collect (When user start typing, active window is FuzzySearchForm. So filter doesn't works correctly. ex: OpHex is active at Disas view but not active at FuzzySearchForm.)
264 | # Cons
265 | # 1. Become slow in case large file.
266 | # 1.1. Re-generate dictionary isn't matter.(But scoring time will be bigger.)
267 | # func ptr and icon id
268 | registered_actions = get_registered_actions()
269 | for action in registered_actions:
270 | # IDA's bug? tilde exists many times in label. ex) Abort -> ~A~bort
271 | # So fix it.
272 | label = get_action_label(action).replace('~', '')
273 | icon = get_action_icon(action)[1]
274 | desctription = get_action_tooltip(action)
275 | if get_action_state(action)[1] > idaapi.AST_ENABLE:
276 | continue
277 | choices[label] = Commands(fptr=process_ui_action, args=[action], description=desctription, icon=icon)
278 |
279 | # Functions()
280 | # Heads()
281 | for n in Names():
282 | demangled = idc.demangle_name(n[1], idc.get_inf_attr(idc.INF_SHORT_DN))
283 | name = demangled if demangled else n[1]
284 | # jump to addr
285 | choices[name] = Commands(fptr=jumpto, args=[n[0]], description="Jump to " + name, icon=124)
286 |
287 | for n in Structs():
288 | choices[n[2]] = Commands(fptr=open_structs_window, args=[n[1]],
289 | description="Jump to Structure definition of " + n[2], icon=52)
290 |
291 | for k, v in choices.items():
292 | names.append(k)
293 |
294 | f = FuzzySearchForm()
295 |
296 | # Compile (in order to populate the controls)
297 | f.Compile()
298 | f.iStr1.value = ""
299 | # Execute the form
300 | ok = f.Execute()
301 |
302 | if ok == 1 and len(f.EChooser.items) > 0:
303 | f.get_selected_item().execute()
304 | # Dispose the form
305 | f.Free()
306 |
307 |
308 | class fuzzy_search_handler(idaapi.action_handler_t):
309 | def __init__(self):
310 | idaapi.action_handler_t.__init__(self)
311 |
312 | def activate(self, ctx):
313 | action = fuzzy_search_main()
314 |
315 | if action:
316 | idaapi.process_ui_action(action)
317 | return 1
318 |
319 | def update(self, ctx):
320 | return idaapi.AST_ENABLE_ALWAYS
321 |
322 |
323 | class FuzzySearchPlugin(idaapi.plugin_t):
324 | flags = idaapi.PLUGIN_FIX | idaapi.PLUGIN_HIDE
325 | comment = "Fuzzy search everything for IDA"
326 | help = "Fuzzy search everything"
327 | wanted_name = "fuzzy search"
328 | wanted_hotkey = ""
329 |
330 | def init(self):
331 | print("Fuzzy Search Plugin loaded.")
332 | idaapi.register_action(
333 | idaapi.action_desc_t("fz:fuzzysearch", "Fuzzy Search", fuzzy_search_handler(), "Shift+SPACE", "", -1))
334 |
335 | return idaapi.PLUGIN_KEEP
336 |
337 | def term(self):
338 | idaapi.unregister_action("fz:fuzzysearch")
339 | pass
340 |
341 | def run(self, arg):
342 | pass
343 |
344 |
345 | def PLUGIN_ENTRY():
346 | return FuzzySearchPlugin()
347 |
--------------------------------------------------------------------------------
/screenshots/idafuzzy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ga-ryo/IDAFuzzy/afd3b34d1fbd2a389f9975de83d5ab46f78aedb6/screenshots/idafuzzy.gif
--------------------------------------------------------------------------------
/screenshots/idafuzzy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ga-ryo/IDAFuzzy/afd3b34d1fbd2a389f9975de83d5ab46f78aedb6/screenshots/idafuzzy.png
--------------------------------------------------------------------------------
/screenshots/jumpf.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ga-ryo/IDAFuzzy/afd3b34d1fbd2a389f9975de83d5ab46f78aedb6/screenshots/jumpf.gif
--------------------------------------------------------------------------------
/screenshots/structdef.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ga-ryo/IDAFuzzy/afd3b34d1fbd2a389f9975de83d5ab46f78aedb6/screenshots/structdef.gif
--------------------------------------------------------------------------------