├── .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 --------------------------------------------------------------------------------