├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── README.md └── findyara.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: oalabs 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: ncipollo/release-action@v1 15 | with: 16 | artifacts: "findyara.py" 17 | omitBody: true 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Apple crap 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![GitHub release](https://img.shields.io/github/v/release/oalabs/findyara-ida.svg)](https://github.com/OALabs/findyara-ida/releases) [![Chat](https://img.shields.io/badge/chat-Discord-blueviolet)](https://discord.gg/cw4U3WHvpn) [![Support](https://img.shields.io/badge/Support-Patreon-FF424D)](https://www.patreon.com/oalabs) 6 | 7 | # FindYara 8 | Use this IDA python plugin to scan your binary with yara rules. All the yara rule matches will be listed with their offset so you can quickly jump to them! 9 | 10 | **:beers: All credit for this plugin and the code goes to David Berard (@_p0ly_) :beers:** 11 | 12 | This plugin is copied from David's excellent [findcrypt-yara plugin](https://github.com/polymorf/findcrypt-yara). This plugin just extends his to use any yara rule. 13 | 14 | ## Using FindYara 15 | The plugin can be launched from the menu using `Edit->Plugins->FindYara` or using the hot-key combination `Ctrl-Alt-Y`. When launched the FindYara will open a file selection dialogue that allows you to select your Yara rules file. Once the rule file has been selected FindYara will scan the loaded binary for rule matches. 16 | 17 | All rule matches are displayed in a selection box that allows you to double click the matches and jump to their location in the binary. 18 | 19 | ### Rules Not Matching Binary 20 | FindYara scans the loaded PE sections in IDA, this means that yara rules that include matches on the PE header **will not match in IDA**. IDA does not load the PE header as a scannable section. Also, if you have not selected `Load resources` when loading your binary in IDA then the resources section will be unavailable for scanning. 21 | 22 | This can lead to frustrating situations where a yara rule will match outside of IDA but not when using FindYara. If you encounter this try editing the yara rule to remove the matches on the PE header and resources sections. 23 | 24 | ## Installing FindYara 25 | Before using the plugin you must install the python Yara module in your IDA environment. The simplest way to do this is to use pip from a shell outside of IDA. 26 | `pip install yara-python`. 27 | 28 | **Do not install the `yara` module by mistake.** The `yara` python module will mess with your `yara-python` module so it must be uninstalled if it was installed by mistake. 29 | 30 | Once you have the yara module installed simply copy the latest release of [`findyara.py`](https://github.com/OALabs/findyara-ida/releases) into your IDA plugins directory and you are ready to start Yara scanning! 31 | 32 | ## ❗Compatibility Issues 33 | FindYara has been developed for use with the __IDA 7+__ and __Python 3__ it is not backwards compatible. 34 | 35 | FindYara requires a the python **Yara** module with version **4+** installed. Earlier versions of Yara are not compatible with the plugin and may cause issues due to breaking changes in the Yara match format. 36 | 37 | ## Acknowledgments 38 | A huge thank you to David Berard (@_p0ly_) - [Follow him on GitHub here](https://github.com/polymorf/)! This is mostly his code and he gets all the credit for the original plugin framework. 39 | 40 | Also, hat tip to Alex Hanel @nullandnull - [Follow him on GitHub here](https://github.com/alexander-hanel). Alex helped me sort through how the IDC methods are being used. His [IDA Python book](https://leanpub.com/IDAPython-Book) is a fantastic reference!! 41 | 42 | -------------------------------------------------------------------------------- /findyara.py: -------------------------------------------------------------------------------- 1 | ######################################################################################## 2 | ## 3 | ## All credit to David Berard (@_p0ly_) https://github.com/polymorf/findcrypt-yara 4 | ## 5 | ## This plugin is simply a copy of his excellent findcrypt-yara plugin only expanded 6 | ## use allow searching for any yara rules. 7 | ## 8 | ## ____ __ __ __ ____ _ _ ___ ____ ___ 9 | ## || || ||\ || || \\ \\// // \\ || \\ // \\ 10 | ## ||== || ||\\|| || )) )/ ||=|| ||_// ||=|| 11 | ## || || || \|| ||_// // || || || \\ || || 12 | ## 13 | ## IDA plugin for Yara scanning... find those Yara matches! 14 | ## 15 | ## Updated for IDA 7.xx and Python 3 16 | ## 17 | ## To install: 18 | ## Copy script into plugins directory, i.e: C:\Program Files\\plugins 19 | ## 20 | ## To run: 21 | ## Ctl+Alt+Y or Edit->Plugins->FindYara 22 | ## Use the dialogue box to select your yara rule file and start scanning! 23 | ## 24 | ######################################################################################## 25 | 26 | import idaapi 27 | import idautils 28 | import ida_bytes 29 | import idc 30 | import ida_kernwin 31 | import yara 32 | import string 33 | 34 | __AUTHOR__ = '@herrcore' 35 | 36 | PLUGIN_NAME = "FindYara" 37 | PLUGIN_HOTKEY = "Ctrl-Alt-Y" 38 | VERSION = '3.3.0' 39 | 40 | try: 41 | class Kp_Menu_Context(idaapi.action_handler_t): 42 | def __init__(self): 43 | idaapi.action_handler_t.__init__(self) 44 | 45 | 46 | @classmethod 47 | def get_name(self): 48 | return self.__name__ 49 | 50 | 51 | @classmethod 52 | def get_label(self): 53 | return self.label 54 | 55 | 56 | @classmethod 57 | def register(self, plugin, label): 58 | self.plugin = plugin 59 | self.label = label 60 | instance = self() 61 | return idaapi.register_action(idaapi.action_desc_t( 62 | self.get_name(), # Name. Acts as an ID. Must be unique. 63 | instance.get_label(), # Label. That's what users see. 64 | instance # Handler. Called when activated, and for updating 65 | )) 66 | 67 | 68 | @classmethod 69 | def unregister(self): 70 | """Unregister the action. 71 | After unregistering the class cannot be used. 72 | """ 73 | idaapi.unregister_action(self.get_name()) 74 | 75 | 76 | @classmethod 77 | def activate(self, ctx): 78 | # dummy method 79 | return 1 80 | 81 | 82 | @classmethod 83 | def update(self, ctx): 84 | if ctx.form_type == idaapi.BWN_DISASM: 85 | return idaapi.AST_ENABLE_FOR_WIDGET 86 | return idaapi.AST_DISABLE_FOR_WIDGET 87 | 88 | 89 | class Searcher(Kp_Menu_Context): 90 | def activate(self, ctx): 91 | self.plugin.search() 92 | return 1 93 | 94 | except: 95 | pass 96 | 97 | p_initialized = False 98 | 99 | 100 | 101 | class YaraSearchResultChooser(idaapi.Choose): 102 | def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False): 103 | idaapi.Choose.__init__( 104 | self, 105 | title, 106 | [ 107 | ["Address", idaapi.Choose.CHCOL_HEX|10], 108 | ["Rule Name", idaapi.Choose.CHCOL_PLAIN|20], 109 | ["Match Name", idaapi.Choose.CHCOL_PLAIN|20], 110 | ["Match", idaapi.Choose.CHCOL_PLAIN|40], 111 | ["Type", idaapi.Choose.CHCOL_PLAIN|10], 112 | ], 113 | flags=flags, 114 | width=width, 115 | height=height, 116 | embedded=embedded) 117 | self.items = items 118 | self.selcount = 0 119 | self.n = len(items) 120 | 121 | 122 | def OnClose(self): 123 | return 124 | 125 | 126 | def OnSelectLine(self, n): 127 | self.selcount += 1 128 | ida_kernwin.jumpto(self.items[n][0]) 129 | 130 | 131 | def OnGetLine(self, n): 132 | res = self.items[n] 133 | res = [idc.atoa(res[0]), res[1], res[2], res[3], res[4]] 134 | return res 135 | 136 | 137 | def OnGetSize(self): 138 | n = len(self.items) 139 | return n 140 | 141 | 142 | def show(self): 143 | return self.Show() >= 0 144 | 145 | #-------------------------------------------------------------------------- 146 | # Plugin 147 | #-------------------------------------------------------------------------- 148 | class FindYara_Plugin_t(idaapi.plugin_t): 149 | comment = "FindYara plugin for IDA Pro (using yara framework)" 150 | help = "" 151 | wanted_name = PLUGIN_NAME 152 | wanted_hotkey = PLUGIN_HOTKEY 153 | flags = idaapi.PLUGIN_KEEP 154 | 155 | 156 | def init(self): 157 | global p_initialized 158 | 159 | # register popup menu handlers 160 | try: 161 | Searcher.register(self, "FindYara") 162 | except: 163 | pass 164 | 165 | if p_initialized is False: 166 | p_initialized = True 167 | idaapi.register_action(idaapi.action_desc_t( 168 | "FindYara", 169 | "Find Yara rule matches!", 170 | self.search, 171 | None, 172 | None, 173 | 0)) 174 | idaapi.attach_action_to_menu("Edit/FindYara", "FindYara", idaapi.SETMENU_APP) 175 | ## Print a nice header 176 | print("=" * 80) 177 | print(" ____ __ __ __ ____ _ _ ___ ____ ___ ") 178 | print(r" || || ||\ || || \\ \\// // \\ || \\ // \\") 179 | print(r" ||== || ||\\|| || )) )/ ||=|| ||_// ||=||") 180 | print(r" || || || \|| ||_// // || || || \\ || ||") 181 | print("\nFindYara v{0} by @herrcore".format(VERSION)) 182 | print("* All credit to David Berard (@_p0ly_) for the code! *") 183 | print("* This is a slightly modified version of findcrypt-yara *") 184 | print("\nFindYara search shortcut key is Ctrl-Alt-y") 185 | print("=" * 80) 186 | 187 | return idaapi.PLUGIN_KEEP 188 | 189 | 190 | def term(self): 191 | pass 192 | 193 | 194 | def toVirtualAddress(self, offset, segments): 195 | va_offset = 0 196 | for seg in segments: 197 | if seg[1] <= offset < seg[2]: 198 | va_offset = seg[0] + (offset - seg[1]) 199 | return va_offset 200 | 201 | 202 | def search(self, yara_file): 203 | memory, offsets = self._get_memory() 204 | try: 205 | rules = yara.compile(yara_file) 206 | except: 207 | print("ERROR: Cannot compile Yara rules from %s" % yara_file) 208 | return 209 | values = self.yarasearch(memory, offsets, rules) 210 | c = YaraSearchResultChooser("FindYara scan results", values) 211 | r = c.show() 212 | 213 | 214 | def yarasearch(self, memory, offsets, rules): 215 | values = list() 216 | matches = rules.match(data=memory) 217 | for rule_match in matches: 218 | name = rule_match.rule 219 | for match in rule_match.strings: 220 | match_string = match[2] 221 | match_type = 'unknown' 222 | if all(chr(c) in string.printable for c in match_string): 223 | match_string = match_string.decode('utf-8') 224 | match_type = 'ascii string' 225 | elif all(chr(c) in string.printable+'\x00' for c in match_string) and (b'\x00\x00' not in match_string): 226 | match_string = match_string.decode('utf-16') 227 | match_type = 'wide string' 228 | else: 229 | match_string = " ".join("{:02x}".format(c) for c in match_string) 230 | match_type = 'binary' 231 | 232 | value = [ 233 | self.toVirtualAddress(match[0], offsets), 234 | name, 235 | match[1], 236 | match_string, 237 | match_type 238 | ] 239 | values.append(value) 240 | return values 241 | 242 | 243 | def _get_memory(self): 244 | result = bytearray() 245 | segment_starts = [ea for ea in idautils.Segments()] 246 | offsets = [] 247 | start_len = 0 248 | for start in segment_starts: 249 | end = idc.get_segm_attr(start, idc.SEGATTR_END) 250 | result += ida_bytes.get_bytes(start, end - start) 251 | offsets.append((start, start_len, len(result))) 252 | start_len = len(result) 253 | return bytes(result), offsets 254 | 255 | 256 | def run(self, arg): 257 | yara_file = ida_kernwin.ask_file(0, "*.yara", 'Choose Yara File...') 258 | if yara_file == None: 259 | print("ERROR: You must choose a yara file to scan with") 260 | else: 261 | self.search(yara_file) 262 | 263 | 264 | # register IDA plugin 265 | def PLUGIN_ENTRY(): 266 | return FindYara_Plugin_t() 267 | --------------------------------------------------------------------------------