├── LICENSE ├── imgs ├── docked.png ├── floating.png ├── tab-1.png ├── tab-2.png └── tab-3.png ├── pySigMaker.py └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 therealzoomgod 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 | -------------------------------------------------------------------------------- /imgs/docked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealzoomgod/pySigMaker/2e455ac9f30742dcde425a3d992866b763f5b901/imgs/docked.png -------------------------------------------------------------------------------- /imgs/floating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealzoomgod/pySigMaker/2e455ac9f30742dcde425a3d992866b763f5b901/imgs/floating.png -------------------------------------------------------------------------------- /imgs/tab-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealzoomgod/pySigMaker/2e455ac9f30742dcde425a3d992866b763f5b901/imgs/tab-1.png -------------------------------------------------------------------------------- /imgs/tab-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealzoomgod/pySigMaker/2e455ac9f30742dcde425a3d992866b763f5b901/imgs/tab-2.png -------------------------------------------------------------------------------- /imgs/tab-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealzoomgod/pySigMaker/2e455ac9f30742dcde425a3d992866b763f5b901/imgs/tab-3.png -------------------------------------------------------------------------------- /pySigMaker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | PLUGIN_VERSION = '0.1.50' 4 | 5 | # If True the starting address will be current function when making a sig for functions. 6 | # SigMaker-x64 behavior is to look for 5 or more references before adding function start 7 | FUNC_START_EA = True 8 | 9 | # Can be set via Gui, this is just for a fallback default 10 | PLUGIN_HOTKEY = 'Ctrl-Alt-S' 11 | 12 | 13 | """ 14 | pySigMaker: 15 | 16 | Ported by: zoomgod - unknowncheats.me 17 | 18 | IDAPython port for most of the origional compiled SigMaker-x64 IDA 19 | plugin with some minor changes, bug-fix and new GUI. 20 | 21 | Credits to the origional author/contributors of SigMaker-x64 22 | https://github.com/ajkhoury/SigMaker-x64 23 | 24 | See readme for IDA/Python requirements 25 | """ 26 | 27 | import sys, pickle, os, shutil 28 | 29 | PLUGIN_DIR, PLUGIN_FILENAME = sys.argv[0].rsplit('/', 1) 30 | HOTKEY_CONFLICT = False 31 | SIGMAKER_X64_PLUGINS = [] 32 | 33 | if PLUGIN_DIR.find('plugins') == -1: 34 | PLUGIN_DIR = '' 35 | else: 36 | if os.path.exists('%s/sigmaker.dll' % PLUGIN_DIR): 37 | SIGMAKER_X64_PLUGINS.append('sigmaker.dll') 38 | 39 | if os.path.exists('%s/sigmaker64.dll' % PLUGIN_DIR): 40 | SIGMAKER_X64_PLUGINS.append('sigmaker64.dll') 41 | 42 | HOTKEY_CONFLICT = len(SIGMAKER_X64_PLUGINS) > 0 43 | 44 | try: 45 | import tkinter 46 | from enum import unique, IntEnum 47 | except: 48 | print('Python 3.4 > required.') 49 | sys.exit(0) 50 | 51 | # Gui 52 | from PyQt5 import Qt, QtCore, QtGui, QtWidgets 53 | 54 | import idc 55 | import idaapi, ida_kernwin 56 | from idaapi import BADADDR 57 | 58 | # Ignore this, just for when I debug gui layout issues 59 | GUI_DBG_ENABLED = False 60 | try: 61 | from SigMakerDebug import QTDebugHelper 62 | GUI_DBG_ENABLED = True 63 | except: 64 | pass 65 | 66 | @unique 67 | class QueryTypes(IntEnum): 68 | QUERY_FIRST = 0 # Return 1st match 69 | QUERY_COUNT = 1 # Return count 70 | QUERY_UNIQUE = 2 # Return True/False 71 | 72 | @unique 73 | class PatternType(IntEnum): 74 | PT_INVALID = -1 75 | PT_DIRECT = 0 76 | PT_FUNCTION = 1 77 | PT_REFERENCE = 2 78 | 79 | @unique 80 | class SigType(IntEnum): 81 | SIG_IDA = 0 82 | SIG_CODE = 1 83 | SIG_OLLY = 2 84 | 85 | @unique 86 | class SigSelect(IntEnum): 87 | OPT_LENGTH = 0 88 | OPT_OPCODES = 1 89 | OPT_WILDCARDS = 2 90 | 91 | @unique 92 | class LogOptions(IntEnum): 93 | LOG_ERROR = 0 94 | LOG_RESULT = 1 95 | LOG_DEBUG = 2 96 | 97 | 98 | # 99 | # 100 | # Utility functions 101 | # 102 | # 103 | 104 | class QueryStruct: 105 | def __init__(self, pattern=b'', mask = b'', startea=BADADDR, endea=BADADDR): 106 | self.pattern = pattern 107 | self.mask = mask 108 | self.startea = startea 109 | self.endea = endea 110 | self.ea = BADADDR 111 | 112 | if self.startea == BADADDR: 113 | self.startea = idaapi.inf_get_min_ea() 114 | 115 | if self.endea == BADADDR: 116 | self.endea = idaapi.inf_get_max_ea() 117 | 118 | def BinSearch(query) -> QueryStruct: 119 | """ 120 | Searches for matching sequence of bytes based on a pattern and mask 121 | 122 | args: 123 | QueryStruct 124 | 125 | returns: 126 | QueryStruct with ea filled in 127 | """ 128 | 129 | startea = query.startea 130 | if query.startea == BADADDR: 131 | query.startea = idaapi.inf_get_min_ea() 132 | 133 | endea = query.endea 134 | if query.startea == BADADDR: 135 | query.startea = idaapi.inf_get_max_ea() 136 | 137 | query.ea = idaapi.bin_search( query.startea, query.endea, query.pattern, query.mask, 138 | idaapi.BIN_SEARCH_FORWARD, 139 | idaapi.BIN_SEARCH_NOBREAK | idaapi.BIN_SEARCH_NOSHOW ) 140 | 141 | return query 142 | 143 | def MakeBin(ida_pattern, startea=BADADDR, endea=BADADDR) -> QueryStruct: 144 | """ 145 | makeBin(ida_pattern) 146 | Returns QueryStruct with bin_search compatible pattern and mask from an IDA style pattern 147 | """ 148 | 149 | patt = bytearray() 150 | mask = bytearray() 151 | 152 | for i in ida_pattern.split(' '): 153 | if i == '?': 154 | patt.append(0) 155 | mask.append(0) 156 | else: 157 | patt.append(int(i, 16)) 158 | mask.append(1) 159 | 160 | return QueryStruct(bytes(patt), bytes(mask), startea, endea) 161 | 162 | def BinQuery(sig, flag = QueryTypes.QUERY_FIRST, startea=None, endea = None): 163 | """ 164 | Args: 165 | sig : IDA style pattern string 166 | flag: One of QueryTypes enum members 167 | 168 | Return types: 169 | flag == QUERY_FIRST returns ea, search stops when matches == 1 170 | flag == QUERY_COUNT returns int, full search 171 | flag == QUERY_UNIQUE returns boolean, search stops when matches > 1 172 | """ 173 | 174 | query = MakeBin(sig) 175 | 176 | Result = [] 177 | 178 | query = BinSearch(query) 179 | while query.ea != BADADDR: 180 | 181 | Result.append(query.ea) 182 | 183 | if flag == QueryTypes.QUERY_UNIQUE and len(Result) > 1: 184 | break 185 | 186 | if flag == QueryTypes.QUERY_FIRST: 187 | return Result[0] 188 | 189 | query.startea = query.ea + 1 190 | ea = BinSearch(query) 191 | 192 | if flag == QueryTypes.QUERY_UNIQUE: 193 | return len(Result) == 1 194 | elif flag == QueryTypes.QUERY_COUNT: 195 | return len(Result) 196 | elif flag == QueryTypes.QUERY_FIRST: 197 | return BADADDR 198 | 199 | raise ValueError('Invalid flag passed') 200 | 201 | # 202 | # 203 | # Pattern converters 204 | # 205 | # 206 | def Ida2Code(sig) -> str: 207 | """ 208 | Ida2Code(sig) 209 | 210 | Convert an IDA sig to code pattern and mask 211 | 212 | Arg: 213 | sig: IDA style sig 214 | 215 | Returns: 216 | string, string 217 | """ 218 | 219 | mask = '' 220 | patt = '' 221 | 222 | for entry in sig.split(' '): 223 | if entry == '?': 224 | patt = patt + '\\x00' 225 | mask = mask + '?' 226 | else: 227 | patt = patt + '\\x%s' % entry 228 | mask = mask + 'x' 229 | 230 | return patt, mask 231 | 232 | def Ida2Olly(sig) -> str: 233 | """ 234 | Ida2Olly(sig) 235 | 236 | Convert an IDA sig to an Olly Debugger compatible sig 237 | 238 | Arg: 239 | sig: IDA style sig 240 | 241 | Return: 242 | string 243 | """ 244 | 245 | pattern = [] 246 | 247 | for entry in sig.split(' '): 248 | if entry == '?': 249 | pattern.append('??') 250 | else: 251 | pattern.append(entry) 252 | 253 | return " ".join(pattern) 254 | 255 | def Code2Ida(patt, mask=None) -> str: 256 | """ 257 | Code2Ida(sig) 258 | 259 | Convert an code style sig to an IDA sig 260 | 261 | Note: When no mask is supplied any \x00 in pattern become a wildcards. 262 | 263 | Arg: 264 | sig : required, code style sig 265 | mask: optional 266 | 267 | Return: 268 | string 269 | """ 270 | 271 | pattern = [] 272 | p = [] 273 | 274 | # convert binary string or regular string into a list of ints 275 | # Since \ is an escape character in Python have to check 276 | # for varying strings 277 | if not type(patt) is type(b''): 278 | if type(patt) is type('') and patt.find('\\') > -1: 279 | p = [ int('0x%s' % x, 16) for x in patt.split('\\x')[1:] ] 280 | else: 281 | return '' 282 | else: 283 | # binary string, can just convert to list 284 | p = list(patt) 285 | 286 | if mask and len(mask) != len(p): 287 | return '' 288 | 289 | for i in range(len(p)): 290 | if mask: 291 | if mask[i] == 'x': 292 | pattern.append('%02X' % p[i]) 293 | else: 294 | pattern.append('?') 295 | elif p[i] > 0: 296 | pattern.append('%02X' % p[i]) 297 | else: 298 | pattern.append('?') 299 | 300 | return ' '.join(pattern) 301 | 302 | def GetIdaSig(sig, mask = None) -> str: 303 | """ 304 | GetIdaSig(sig) 305 | 306 | Converts Olly or Code style sigs to an IDA style sigs 307 | 308 | Arg: 309 | sig : required, olly or code style sig 310 | mask: optional, valid only for code sigs 311 | 312 | Return: 313 | string 314 | """ 315 | 316 | # Only a code sig should be byte string 317 | if type(sig) is type(b''): 318 | return Code2Ida(sig, mask) 319 | 320 | if sig.find(' ') > -1: 321 | 322 | # an olly sig without wildcards would be same as an ida sig so this is safe 323 | if sig.find(' ?? ') > -1: 324 | return sig.replace('??', '?') 325 | 326 | # Olly sig with no wildcards or already an ida sig 327 | return sig 328 | 329 | # Only supported type left is code sigs as a string 330 | return Code2Ida(sig, mask) 331 | 332 | def GetSigType(sig) -> SigType: 333 | 334 | if type(sig) is type(b'') or sig.find('\\') > -1: 335 | return SigType.SIG_CODE 336 | 337 | if sig.find(' ') > -1: 338 | if sig.find(' ?? ') > -1: 339 | return SigType.SIG_OLLY 340 | return SigType.SIG_IDA 341 | 342 | return SigType.SIG_CODE 343 | 344 | # 345 | # 346 | # SigMaker 347 | # 348 | # 349 | class SigCreateStruct: 350 | def __init__(self): 351 | self.sig = [] 352 | self.dwOrigStartAddress = BADADDR #ea at cursor when started 353 | self.dwStartAddress = BADADDR 354 | self.dwCurrentAddress = BADADDR 355 | self.bUnique = False 356 | self.iOpCount = 0 357 | self.eType = PatternType.PT_INVALID 358 | 359 | class SigMaker: 360 | """ 361 | Public methods: 362 | AutoFunction() 363 | AutoAddress() 364 | """ 365 | 366 | def __init__(self, plugin): 367 | self.__plugin = plugin 368 | self.Sigs = [] 369 | 370 | def _reset(self): 371 | self.Sigs = [] 372 | 373 | def _addBytesToSig(self, sigIndex, ea, size): 374 | 375 | for i in range(0, size): 376 | b = idaapi.get_byte( ea + i ) 377 | self.Sigs[sigIndex].sig.append('%02X' % b) 378 | 379 | def _addWildcards(self, sigIndex, count): 380 | for i in range(0, count): 381 | self.Sigs[sigIndex].sig.append('?') 382 | 383 | def _getCurrentOpcodeSize(self, cmd) -> (int, int): 384 | 385 | count = 0 386 | 387 | for i in range(0, idaapi.UA_MAXOP): 388 | 389 | count = i 390 | if cmd.ops[i].type == idaapi.o_void: 391 | return 0, count 392 | 393 | if cmd.ops[i].offb != 0: 394 | return cmd.ops[i].offb, count 395 | 396 | return 0, count 397 | 398 | def _matchOperands(self, ea) -> bool: 399 | 400 | if idaapi.get_first_dref_from(ea) != BADADDR: 401 | return False 402 | elif not self.__plugin.Settings.bOnlyReliable: 403 | if idaapi.get_first_fcref_from(ea) != BADADDR: 404 | return False 405 | elif idaapi.get_first_cref_from(ea) != BADADDR: 406 | return False 407 | 408 | return True 409 | 410 | def _addInsToSig(self, cmd, sigIndex): 411 | 412 | size, count = self._getCurrentOpcodeSize(cmd) 413 | 414 | if size == 0: 415 | self._addBytesToSig(sigIndex, cmd.ea, cmd.size) 416 | return 417 | else: 418 | self._addBytesToSig(sigIndex, cmd.ea, size) 419 | 420 | if self._matchOperands(cmd.ea): 421 | self._addBytesToSig(sigIndex, cmd.ea + size, cmd.size - size) 422 | else: 423 | self._addWildcards(sigIndex, cmd.size - size) 424 | 425 | def _addToSig(self, sigIndex) -> bool: 426 | 427 | cmd = idaapi.insn_t() 428 | cmd.size = 0 429 | 430 | sig = self.Sigs[sigIndex] 431 | 432 | if not idaapi.can_decode(sig.dwCurrentAddress): 433 | return False 434 | 435 | count = idaapi.decode_insn(cmd, sig.dwCurrentAddress) 436 | 437 | if count == 0 or cmd.size == 0: 438 | return False 439 | 440 | if cmd.size < 5: 441 | self._addBytesToSig(sigIndex, sig.dwCurrentAddress, cmd.size) 442 | else: 443 | self._addInsToSig(cmd, sigIndex) 444 | 445 | sig.dwCurrentAddress = sig.dwCurrentAddress + cmd.size 446 | sig.iOpCount = sig.iOpCount + 1 447 | 448 | self.Sigs[sigIndex] = sig 449 | 450 | return True 451 | 452 | def _haveUniqueSig(self) -> bool: 453 | for i in range(0, len(self.Sigs)): 454 | if self.Sigs[i].bUnique: 455 | return True 456 | return False 457 | 458 | def _addRefs(self, startea) -> bool: 459 | 460 | self.__plugin.log('Adding references', LogOptions.LOG_DEBUG) 461 | 462 | if idaapi.get_func_num(startea) != -1: 463 | sig = SigCreateStruct() 464 | sig.dwStartAddress = startea 465 | sig.dwCurrentAddress = startea 466 | sig.eType = PatternType.PT_DIRECT 467 | self.Sigs.append(sig) 468 | self.__plugin.log('Added direct reference 0x%X' % startea, LogOptions.LOG_DEBUG) 469 | 470 | eaCurrent = idaapi.get_first_cref_to(startea) 471 | while eaCurrent != BADADDR: 472 | 473 | if eaCurrent != startea: 474 | sig = SigCreateStruct() 475 | sig.dwStartAddress = eaCurrent 476 | sig.dwCurrentAddress = eaCurrent 477 | sig.eType = PatternType.PT_REFERENCE 478 | self.Sigs.append(sig) 479 | self.__plugin.log('Added reference 0x%X' % eaCurrent, LogOptions.LOG_DEBUG) 480 | 481 | if self.__plugin.Settings.maxRefs > 0 and len(self.Sigs) >= self.__plugin.Settings.maxRefs: 482 | break 483 | 484 | eaCurrent = idaapi.get_next_cref_to(startea, eaCurrent) 485 | 486 | if len(self.Sigs) < 5: 487 | 488 | self.__plugin.log('Not enough references were found (%i so far), trying the function.' % len(self.Sigs), LogOptions.LOG_DEBUG) 489 | 490 | func = idaapi.get_func(startea) 491 | 492 | if not func or func.start_ea == BADADDR: 493 | self.__plugin.log('Selected address not in a valid function.', LogOptions.LOG_ERROR) 494 | return False 495 | 496 | if func.start_ea != startea: 497 | 498 | eaCurrent = idaapi.get_first_cref_to(func.start_ea) 499 | 500 | while eaCurrent != BADADDR: 501 | 502 | if eaCurrent != startea: 503 | sig = SigCreateStruct() 504 | sig.dwStartAddress = func.start_ea 505 | sig.dwCurrentAddress = eaCurrent 506 | sig.eType = PatternType.PT_FUNCTION 507 | self.Sigs.append(sig) 508 | self.__plugin.log('Added function 0x%X' % eaCurrent, LogOptions.LOG_DEBUG) 509 | 510 | if self.__plugin.Settings.maxRefs > 0 and len(self.Sigs) >= self.__plugin.Settings.maxRefs: 511 | break 512 | 513 | eaCurrent = idaapi.get_next_cref_to(func.start_ea, eaCurrent) 514 | 515 | if not len(self.Sigs): 516 | self.__plugin.log('Automated signature generation failed, no references found.', LogOptions.LOG_ERROR) 517 | return False 518 | 519 | self.__plugin.log('Added %i references.' % len(self.Sigs), LogOptions.LOG_DEBUG) 520 | 521 | return True 522 | 523 | def _chooseSig(self) -> bool: 524 | 525 | max = 9999 526 | selected = -1 527 | 528 | for sigIndex in range(0, len(self.Sigs)): 529 | 530 | sig = self.Sigs[sigIndex] 531 | 532 | # drop wildcards off end of sig 533 | while sig.sig[-1] == '?': 534 | sig.sig = sig.sig[:-1] 535 | 536 | if sig.bUnique: 537 | 538 | sigLen = len(sig.sig) 539 | 540 | if self.__plugin.Settings.SigSelect == SigSelect.OPT_LENGTH: 541 | if sigLen < max or (sig.eType == PatternType.PT_DIRECT and max == sigLen): 542 | max = sigLen 543 | selected = sigIndex 544 | else: 545 | if self.__plugin.Settings.SigSelect == SigSelect.OPT_OPCODES: 546 | if sig.iOpCount < max or (sig.eType == PatternType.PT_DIRECT and max == sig.iOpCount): 547 | max = sig.iOpCount 548 | selected = sigIndex 549 | else: 550 | wildcards = ''.join(sig.sig).count('?') 551 | if wildcards < max or sig.eType == PatternType.PT_DIRECT and max == wildcards: 552 | selected = sigIndex 553 | max = wildcards 554 | 555 | if selected == -1: 556 | self.__plugin.log('Failed to create signature.', LogOptions.LOG_ERROR) 557 | return False 558 | 559 | sig = self.Sigs[selected] 560 | idaSig = ' '.join(sig.sig) 561 | strSig = '' 562 | 563 | if self.__plugin.Settings.SigType == SigType.SIG_CODE: 564 | patt, mask = Ida2Code(idaSig) 565 | strSig = patt + ' ' + mask 566 | elif self.__plugin.Settings.SigType == SigType.SIG_OLLY: 567 | strSig = Ida2Olly(idaSig) 568 | else: 569 | strSig = idaSig 570 | 571 | # 572 | # Testing sigs for now, may just leave it, it's quick 573 | # 574 | ea = BinQuery(idaSig, QueryTypes.QUERY_FIRST) 575 | 576 | txt = '' 577 | 578 | if sig.eType == PatternType.PT_DIRECT: 579 | txt = 'result: matches @ 0x%X, sig direct: %s' % (ea, strSig) 580 | elif sig.eType == PatternType.PT_FUNCTION: 581 | txt = 'result: matches @ 0x%X, sig function: (+0x%X) %s' % (ea, startea - sig.dwStartAddress, strSig) 582 | elif sig.eType == PatternType.PT_REFERENCE: 583 | txt = 'result: matches @ 0x%X, sig reference: %s' % (ea, strSig) 584 | 585 | self.__plugin.log(txt, LogOptions.LOG_RESULT) 586 | 587 | # 588 | # Qt has a clipboard widget but I didn't want to place a QT 589 | # requirement on using the class since it has nothing to do 590 | # with the Gui. TKinter is included with Python. 591 | # 592 | r = tkinter.Tk() 593 | r.withdraw() 594 | r.clipboard_clear() 595 | r.clipboard_append(strSig) 596 | r.update() 597 | r.destroy() 598 | 599 | return True 600 | 601 | def AutoFunction(self) -> bool: 602 | """ 603 | Generate shortest unique signature possible to current function 604 | """ 605 | 606 | self._reset() 607 | 608 | startea = idc.get_screen_ea() 609 | if startea in [0, BADADDR]: 610 | self.__plugin.log('Current ea == BADADDR.', LogOptions.LOG_ERROR) 611 | return False 612 | 613 | if FUNC_START_EA: 614 | # Get function start 615 | func = idaapi.get_func(startea) 616 | if not func or func.start_ea == BADADDR: 617 | self.__plugin.log('Must be in a function.', LogOptions.LOG_ERROR) 618 | return False 619 | elif startea != func.start_ea: 620 | startea = func.start_ea 621 | self.__plugin.log('Using function: 0x%X' % startea, LogOptions.LOG_DEBUG) 622 | 623 | if not self._addRefs(startea): 624 | return False 625 | 626 | iCount = 0 627 | bHaveUniqueSig = False 628 | 629 | while not bHaveUniqueSig and len(self.Sigs): 630 | 631 | for sigIndex in range(0, len(self.Sigs)): 632 | 633 | if len(self.Sigs[sigIndex].sig) < self.__plugin.Settings.maxSigLength and self._addToSig(sigIndex): 634 | if len(self.Sigs[sigIndex].sig) > 5: 635 | self.Sigs[sigIndex].bUnique = BinQuery(' '.join(self.Sigs[sigIndex].sig), QueryTypes.QUERY_UNIQUE) 636 | else: 637 | #return False 638 | if sigIndex == 0: 639 | self.Sigs = self.Sigs[1:] 640 | elif sigIndex == len(self.Sigs) - 1: 641 | self.Sigs = self.Sigs[:-1] 642 | else: 643 | self.Sigs = self.Sigs[:sigIndex] + self.Sigs[sigIndex+1:] 644 | 645 | sigIndex = sigIndex - 1 646 | 647 | bHaveUniqueSig = self._haveUniqueSig() 648 | 649 | return self._chooseSig() 650 | 651 | def AutoAddress(self) -> bool: 652 | """ 653 | Rather than create a sig from selection this 654 | gets current ea from screen and then creates 655 | the shortest sig possible. 656 | 657 | I don't really see a need for making sigs from a 658 | selection but can add it if enough people need it. 659 | """ 660 | 661 | self._reset() 662 | 663 | startea = idc.get_screen_ea() 664 | if startea in [0, BADADDR]: 665 | self.__plugin.log('Click on address you want sig for.', LogOptions.LOG_ERROR) 666 | return False 667 | 668 | sig = SigCreateStruct() 669 | sig.dwStartAddress = startea 670 | sig.dwCurrentAddress = startea 671 | sig.eType = PatternType.PT_DIRECT 672 | 673 | self.Sigs.append(sig) 674 | 675 | while not self.Sigs[0].bUnique and len(self.Sigs[0].sig) < self.__plugin.Settings.maxSigLength: 676 | 677 | sigIndex = 0 678 | if self._addToSig(sigIndex): 679 | if len(self.Sigs[sigIndex].sig) > 5: 680 | self.Sigs[sigIndex].bUnique = BinQuery(' '.join(self.Sigs[sigIndex].sig), QueryTypes.QUERY_UNIQUE) 681 | else: 682 | self.__plugin.log('Unable to create sig at selected address', LogOptions.LOG_ERROR) 683 | return False 684 | 685 | self._chooseSig() 686 | 687 | # 688 | # 689 | # QT Gui 690 | # 691 | # 692 | class PluginGui(idaapi.PluginForm): 693 | 694 | def __init__(self, plugin): 695 | 696 | global GUI_DBG_ENABLED 697 | 698 | idaapi.PluginForm.__init__(self) 699 | self.__plugin = plugin 700 | 701 | if GUI_DBG_ENABLED: 702 | self._QtDbgHelper = QTDebugHelper(self) 703 | else: 704 | self._QtDbgHelper = None 705 | 706 | self.closed = False 707 | 708 | # 709 | # IDA PluginForm overloaded methods 710 | # 711 | def Show(self, caption, options=None): 712 | if options: 713 | super().Show(caption, options) 714 | return 715 | # Floating window as default. 716 | super().Show(caption, idaapi.PluginForm.WOPN_DP_FLOATING) 717 | 718 | def OnCreate(self, form): 719 | self.widget = self.FormToPyQtWidget(form) 720 | self.PopulateForm() 721 | 722 | # Bit hackish but is needed to restore form position/size. 723 | # Parent widget isn't set until after this function returns. 724 | # The passed form is a child under the main TWidget created by IDA 725 | QtCore.QTimer.singleShot(1000, self._formState) 726 | 727 | def OnClose(self, form): 728 | self._formState(bSave=True) 729 | self.closed = True 730 | 731 | # 732 | # Connected QT events 733 | # 734 | def _sigTypeIdaClick(self): 735 | self.__plugin.Settings.SigType = SigType.SIG_IDA 736 | self.__plugin.Settings.save() 737 | 738 | def _sigTypeCodeClick(self): 739 | self.__plugin.Settings.SigType = SigType.SIG_CODE 740 | self.__plugin.Settings.save() 741 | 742 | def _sigTypeOllyClick(self): 743 | self.__plugin.Settings.SigType = SigType.SIG_OLLY 744 | self.__plugin.Settings.save() 745 | 746 | def _sigTest(self): 747 | 748 | patt = self.patt.currentText() 749 | mask = self.mask.text() 750 | 751 | sig = '' 752 | st = GetSigType(patt) 753 | 754 | if st == SigType.SIG_CODE: 755 | sig = GetIdaSig(patt, mask) 756 | else: 757 | sig = GetIdaSig(patt) 758 | mask = '' 759 | 760 | if not sig: 761 | self.__plugin.log('Invalid sig: "%s"' % sig, LogOptions.LOG_ERROR) 762 | return 763 | 764 | self.__plugin.Settings.addHistory(patt, mask) 765 | self.__plugin.Settings.save() 766 | 767 | query = MakeBin(sig) 768 | result = BinSearch(query) 769 | 770 | # 771 | # Always logging tests to output so set to LOG_ERROR 772 | # 773 | if result != BADADDR: 774 | self.__plugin.log('Sig matched @ 0x%X' % result.ea, LogOptions.LOG_ERROR) 775 | else: 776 | self.__plugin.log('No match found', LogOptions.LOG_ERROR) 777 | 778 | def _sigTestSelectChanged(self, index): 779 | 780 | mask = '' 781 | try: 782 | mask = self.__plugin.Settings.getHistory()[index][1] 783 | except: 784 | pass 785 | 786 | self.mask.setText(mask) 787 | 788 | def _sigCurrentFunction(self): 789 | self.__plugin.SigMaker.AutoFunction() 790 | 791 | def _sigAtCursor(self): 792 | self.__plugin.SigMaker.AutoAddress() 793 | 794 | def _logLevelChanged(self, index): 795 | self.__plugin.Settings.LogLevel = index 796 | self.__plugin.Settings.save() 797 | 798 | def _sigSelectChanged(self, index): 799 | self.__plugin.Settings.SigSelect = index 800 | self.__plugin.Settings.save() 801 | 802 | def _safeDataChecked(self, checkedState): 803 | # 804 | # Checkboxes can be tristate so passed arg is not a bool 805 | # 806 | if checkedState == QtCore.Qt.Unchecked: 807 | self.__plugin.Settings.bOnlyReliable = False 808 | else: 809 | self.__plugin.Settings.bOnlyReliable = True 810 | 811 | self.__plugin.Settings.save() 812 | 813 | def _archiveSigmaker(self): 814 | 815 | global PLUGIN_DIR, HOTKEY_CONFLICT, SIGMAKER_X64_PLUGINS 816 | 817 | bDidMove = False 818 | 819 | for name in SIGMAKER_X64_PLUGINS: 820 | 821 | if not os.path.exists('%s/orig_sigmaker' % PLUGIN_DIR): 822 | self.__plugin.log('mkdir: %s/orig_sigmaker' % (PLUGIN_DIR), LogOptions.LOG_ERROR) 823 | os.mkdir('%s/orig_sigmaker' % PLUGIN_DIR) 824 | 825 | if os.path.isfile('%s/%s' % (PLUGIN_DIR, name)): 826 | shutil.move('%s/%s' % (PLUGIN_DIR, name), '%s/orig_sigmaker/%s' % (PLUGIN_DIR, name)) 827 | bDidMove = True 828 | self.__plugin.log('Moved: %s/%s to %s/orig_sigmaker/%s' % (PLUGIN_DIR, name, PLUGIN_DIR, name), LogOptions.LOG_ERROR) 829 | 830 | if bDidMove: 831 | self.__plugin.log('SigMaker-x64 archived, restart IDA to unload it', LogOptions.LOG_ERROR) 832 | HOTKEY_CONFLICT = False 833 | SIGMAKER_X64_PLUGINS = [] 834 | self.archiveBtn.setEnabled(False) 835 | 836 | def _saveHotkey(self): 837 | hotkey = self.hotkeyTxt.text() 838 | if hotkey != self.__plugin.Settings.hotkey: 839 | self.__plugin.Settings.hotkey = hotkey 840 | self.__plugin.Settings.save() 841 | self.__plugin.log('\npySigMaker hotkey changed to %s, IDA restart needed.' % hotkey, LogOptions.LOG_ERROR) 842 | 843 | def _defaultHotkey(self): 844 | global PLUGIN_HOTKEY 845 | self.hotkeyTxt.setText(PLUGIN_HOTKEY) 846 | if PLUGIN_HOTKEY != self.__plugin.Settings.hotkey: 847 | self.__plugin.Settings.hotkey = PLUGIN_HOTKEY 848 | self.__plugin.Settings.save() 849 | self.__plugin.log('\npySigMaker hotkey changed to %s (default), IDA restart needed.' % PLUGIN_HOTKEY, LogOptions.LOG_ERROR) 850 | 851 | # 852 | # Save/Restore plugin form position and size. 853 | # 854 | def _formState(self, bSave=False): 855 | 856 | def getWidget(): 857 | widget, parent = None, self.widget 858 | while parent: 859 | if parent.windowTitle() == self.widget.windowTitle(): 860 | widget = parent 861 | parent = parent.parent() 862 | return widget 863 | 864 | widget = getWidget() 865 | if not widget: 866 | self.__plugin.log('Failed to save form info', LogOptions.LOG_ERROR) 867 | return 868 | 869 | if bSave: 870 | qrect = widget.geometry() 871 | x, y, w, h = qrect.x(), qrect.y(), qrect.width(), qrect.height() 872 | self.__plugin.Settings.saveFormInfo(x, y, w, h) 873 | self.__plugin.Settings.save() 874 | self.__plugin.log('Form saved, x={}, y={}, w={}, h={}'.format(x, y, w, h), LogOptions.LOG_DEBUG) 875 | else: 876 | x, y, w, h = self.__plugin.Settings.getFormInfo() 877 | if x > -1: 878 | widget.setGeometry(x, y, w, h) 879 | self.__plugin.log('Form restored: x={}, y={}, w={}, h={}'.format(x, y, w, h), LogOptions.LOG_DEBUG) 880 | self.patt.setCurrentText('') 881 | 882 | # 883 | # QT widget creation 884 | # 885 | def _getSigTypesBox(self): 886 | """ Sig type selector""" 887 | 888 | setting = self.__plugin.Settings.SigType 889 | 890 | grp = QtWidgets.QGroupBox("Sig Type") 891 | 892 | r1 = QtWidgets.QRadioButton(" IDA ") 893 | r2 = QtWidgets.QRadioButton(" Code ") 894 | r3 = QtWidgets.QRadioButton(" Olly ") 895 | 896 | r1.toggled.connect(self._sigTypeIdaClick) 897 | r2.toggled.connect(self._sigTypeCodeClick) 898 | r3.toggled.connect(self._sigTypeOllyClick) 899 | 900 | if setting == SigType.SIG_IDA: 901 | r1.setChecked(True) 902 | elif setting == SigType.SIG_CODE: 903 | r2.setChecked(True) 904 | elif setting == SigType.SIG_OLLY: 905 | r3.setChecked(True) 906 | 907 | layout = QtWidgets.QHBoxLayout() 908 | layout.addWidget(r1) 909 | layout.addWidget(r2) 910 | layout.addWidget(r3) 911 | 912 | grp.setLayout(layout) 913 | 914 | return grp 915 | 916 | def _initSettings(self, layout): 917 | 918 | global HOTKEY_CONFLICT 919 | 920 | formLayout = QtWidgets.QFormLayout() 921 | # 922 | # Log to output window options 923 | # 924 | self.logOpt = QtWidgets.QComboBox() 925 | for s in ['Errors', 'Errors/Results', 'Debug']: 926 | self.logOpt.addItem(s) 927 | 928 | if self.__plugin.Settings.LogLevel > LogOptions.LOG_DEBUG: 929 | self.__plugin.Settings.LogLevel = LogOptions.LOG_DEBUG 930 | elif self.__plugin.Settings.LogLevel < LogOptions.LOG_ERROR: 931 | self.__plugin.Settings.LogLevel = LogOptions.LOG_ERROR 932 | 933 | self.logOpt.setCurrentIndex(self.__plugin.Settings.LogLevel) 934 | self.logOpt.currentIndexChanged.connect(self._logLevelChanged) 935 | 936 | # 937 | # Selecting sig from results options 938 | # 939 | self.sigSelectorOpt = QtWidgets.QComboBox() 940 | for s in ['Shortest Sig', 'Least Opcodes', 'Least Wildcards']: 941 | self.sigSelectorOpt.addItem(s) 942 | 943 | if self.__plugin.Settings.SigSelect > SigSelect.OPT_WILDCARDS: 944 | self.__plugin.Settings.SigSelect = SigSelect.OPT_WILDCARDS 945 | elif self.__plugin.Settings.SigSelect < SigSelect.OPT_LENGTH: 946 | self.__plugin.Settings.SigSelect = SigSelect.OPT_LENGTH 947 | 948 | self.sigSelectorOpt.setCurrentIndex(self.__plugin.Settings.SigSelect) 949 | self.sigSelectorOpt.currentIndexChanged.connect(self._sigSelectChanged) 950 | 951 | # 952 | # Reliable/Unreliable data option 953 | # 954 | self.safeData = QtWidgets.QCheckBox() 955 | self.safeData.setTristate(False) 956 | 957 | if self.__plugin.Settings.bOnlyReliable: 958 | self.safeData.setCheckState(QtCore.Qt.Checked) 959 | else: 960 | self.safeData.setCheckState(QtCore.Qt.Unchecked) 961 | 962 | self.safeData.stateChanged.connect(self._safeDataChecked) 963 | 964 | if HOTKEY_CONFLICT: 965 | self.archiveBtn = QtWidgets.QPushButton('Archive SigMaker-x64') 966 | self.archiveBtn.clicked.connect(self._archiveSigmaker) 967 | 968 | formLayout.addRow('Output', self.logOpt) 969 | formLayout.addRow('Sig Choice', self.sigSelectorOpt) 970 | formLayout.addRow('Reliable Data Only', self.safeData) 971 | 972 | layout.addLayout(formLayout) 973 | 974 | layout2 = QtWidgets.QHBoxLayout() 975 | 976 | lbl = QtWidgets.QLabel('Hotkey:') 977 | 978 | self.hotkeyTxt = QtWidgets.QLineEdit() 979 | self.hotkeyTxt.setText(self.__plugin.Settings.hotkey) 980 | self.hotkeySetBtn = QtWidgets.QPushButton('Set') 981 | self.hotkeyRestoreBtn = QtWidgets.QPushButton('Default') 982 | self.hotkeySetBtn.clicked.connect(self._saveHotkey) 983 | self.hotkeyRestoreBtn.clicked.connect(self._defaultHotkey) 984 | 985 | layout2.addWidget(lbl) 986 | layout2.addWidget(self.hotkeyTxt) 987 | layout2.addWidget(self.hotkeySetBtn) 988 | layout2.addWidget(self.hotkeyRestoreBtn) 989 | 990 | layout.addLayout(layout2) 991 | 992 | if HOTKEY_CONFLICT: 993 | layout.addWidget(self.archiveBtn) 994 | 995 | def _initMainTab(self): 996 | 997 | tab = QtWidgets.QWidget() 998 | layout = QtWidgets.QVBoxLayout() 999 | 1000 | btn1 = QtWidgets.QPushButton(' Sig for current function ') 1001 | btn2 = QtWidgets.QPushButton(' Sig at current cursor position ') 1002 | 1003 | btn1.clicked.connect(self._sigCurrentFunction) 1004 | btn2.clicked.connect(self._sigAtCursor) 1005 | 1006 | layout.addWidget(btn1) 1007 | layout.addWidget(btn2) 1008 | layout.addWidget(self._getSigTypesBox()) 1009 | 1010 | tab.setLayout(layout) 1011 | self.tabControl.addTab(tab, 'Create Sigs') 1012 | 1013 | def _initSigTest(self): 1014 | 1015 | tab = QtWidgets.QWidget() 1016 | layout = QtWidgets.QFormLayout() 1017 | 1018 | # Need refs to patt and mask controls 1019 | self.patt = QtWidgets.QComboBox() 1020 | self.patt.setEditable(True) 1021 | self.patt.setCurrentText("") 1022 | 1023 | self.mask = QtWidgets.QLineEdit() 1024 | self.mask.setText("") 1025 | 1026 | self.patt.setInsertPolicy(QtWidgets.QComboBox.NoInsert) 1027 | 1028 | sigs = [ x[0] for x in self.__plugin.Settings.getHistory() ] 1029 | self.patt.addItems(sigs) 1030 | 1031 | btn = QtWidgets.QPushButton(' Test ') 1032 | btn.clicked.connect(self._sigTest) 1033 | 1034 | self.patt.currentIndexChanged.connect(self._sigTestSelectChanged) 1035 | 1036 | layout.addRow('Patt', self.patt) 1037 | layout.addRow('Mask', self.mask) 1038 | layout.addRow('', btn) 1039 | 1040 | tab.setLayout(layout) 1041 | self.tabControl.addTab(tab, 'Test Sigs') 1042 | 1043 | def _initSettingsTab(self): 1044 | tab = QtWidgets.QWidget() 1045 | layout = QtWidgets.QVBoxLayout() 1046 | self._initSettings(layout) 1047 | tab.setLayout(layout) 1048 | self.tabControl.addTab(tab, 'Settings') 1049 | 1050 | def PopulateForm(self): 1051 | 1052 | layout = QtWidgets.QVBoxLayout() 1053 | self.tabControl = QtWidgets.QTabWidget(self.widget) 1054 | layout.addWidget(self.tabControl) 1055 | self.widget.setLayout(layout) 1056 | 1057 | self._initMainTab() 1058 | self._initSigTest() 1059 | self._initSettingsTab() 1060 | 1061 | if self._QtDbgHelper: 1062 | self._QtDbgHelper.initDebugTab() 1063 | 1064 | return 1065 | 1066 | 1067 | # 1068 | # Shared settings class 1069 | # 1070 | class PluginSettings: 1071 | 1072 | def __init__(self, plugin): 1073 | 1074 | global PLUGIN_HOTKEY 1075 | 1076 | self.__plugin = plugin 1077 | self.__loaded = False 1078 | self.__configName = idaapi.get_user_idadir() + '\\pySigMaker.config' 1079 | 1080 | # 0 disables limit 1081 | self.maxRefs = 0 1082 | 1083 | # False creates less reliable sigs IE: they break easier on updates 1084 | self.bOnlyReliable = True 1085 | 1086 | # Controls how sig is selected from multiple unique sigs 1087 | self.SigSelect = SigSelect.OPT_LENGTH 1088 | 1089 | # Type of sig to return 1090 | self.SigType = SigType.SIG_IDA 1091 | 1092 | self.LogLevel = LogOptions.LOG_ERROR 1093 | 1094 | # Max sig length IE: 'E9 ?' is length of 2 1095 | self.maxSigLength = 100 1096 | 1097 | # Sig test history 1098 | self._history = [] 1099 | 1100 | # default hot key 1101 | self.hotkey = PLUGIN_HOTKEY 1102 | 1103 | # form info 1104 | self.x = -1 1105 | self.y = -1 1106 | self.w = -1 1107 | self.h = -1 1108 | 1109 | def saveFormInfo(self, x, y, w, h): 1110 | self.x = x 1111 | self.y = y 1112 | self.w = w 1113 | self.h = h 1114 | 1115 | def getFormInfo(self): 1116 | return self.x, self.y, self.w, self.h 1117 | 1118 | def getHistory(self): 1119 | return self._history 1120 | 1121 | def addHistory(self, sig, mask=''): 1122 | 1123 | # Move to front of list when used, limit history to last 10 entries 1124 | hist = [[sig, mask]] 1125 | 1126 | for p, m in self._history: 1127 | if p == sig: 1128 | continue 1129 | hist.append([p, m]) 1130 | if len(hist) == 10: 1131 | break 1132 | 1133 | self._history = hist 1134 | 1135 | def load(self): 1136 | 1137 | if self.__loaded: 1138 | return 1139 | 1140 | if not os.path.exists(self.__configName): 1141 | self.__plugin.log('pySigMaker: Using defaults', LogOptions.LOG_ERROR) 1142 | return False 1143 | 1144 | if not os.path.isfile(self.__configName): 1145 | self.__plugin.log('pySigMaker: Using defaults', LogOptions.LOG_ERROR) 1146 | return False 1147 | 1148 | 1149 | d = {} 1150 | fh = None 1151 | 1152 | try: 1153 | fh = open(self.__configName, 'rb') 1154 | d = pickle.load(fh) 1155 | except: 1156 | self.__plugin.log('pySigMaker: Cfg corrupt, using defaults', LogOptions.LOG_ERROR) 1157 | 1158 | if fh: 1159 | fh.close() 1160 | 1161 | for k, v in d.items(): 1162 | if k in self.__dict__: 1163 | self.__dict__[k] = v 1164 | self.__plugin.log('cfg-load: {0} {1} {2}'.format(k, v, type(v)), LogOptions.LOG_DEBUG) 1165 | 1166 | self.__loaded = True 1167 | return d != {} 1168 | 1169 | def save(self): 1170 | 1171 | d = {} 1172 | for k, v in self.__dict__.items(): 1173 | if k.find('__') > -1: 1174 | continue 1175 | d[k] = v 1176 | self.__plugin.log('cfg-save: {0} {1} {2}'.format(k, v, type(v)), LogOptions.LOG_DEBUG) 1177 | 1178 | fh = None 1179 | 1180 | try: 1181 | fh = open(self.__configName, 'wb') 1182 | pickle.dump(d, fh) 1183 | except: 1184 | self.__plugin.log('pySigMaker: Failed to save config', LogOptions.LOG_ERROR) 1185 | 1186 | if fh: 1187 | fh.close() 1188 | 1189 | class SigMakerPlugin: 1190 | 1191 | def __init__(self): 1192 | self.Settings = PluginSettings(self) 1193 | self.Gui = PluginGui(self) 1194 | self.SigMaker = SigMaker(self) 1195 | self.Settings.load() 1196 | 1197 | def log(self, msg, log_level = LogOptions.LOG_RESULT): 1198 | if log_level == LogOptions.LOG_ERROR or log_level <= self.Settings.LogLevel: 1199 | print(msg) 1200 | 1201 | def showGui(self): 1202 | if not self.Gui or self.Gui.closed: 1203 | self.Gui = PluginGui(self) 1204 | self.Gui.Show('pySigMaker') 1205 | 1206 | def banner(hotkey): 1207 | print('---------------------------------------------------------------------------------------------') 1208 | print('pySigMaker: zoomgod - unknowncheats.me (credit to ajkhoury for origional SigMaker-X64 code)') 1209 | print(' v%s - hotkey: %s' % (PLUGIN_VERSION, hotkey)) 1210 | print('---------------------------------------------------------------------------------------------') 1211 | 1212 | # 1213 | # IDA Plugin Loader 1214 | # 1215 | 1216 | gsigmaker = SigMakerPlugin() 1217 | banner(gsigmaker.Settings.hotkey) 1218 | 1219 | def PLUGIN_ENTRY(): 1220 | return sigmaker_t() 1221 | 1222 | class sigmaker_t(idaapi.plugin_t): 1223 | 1224 | flags = idaapi.PLUGIN_UNL 1225 | comment = 'Creates code signature patterns.' 1226 | help = '' 1227 | wanted_name = 'pySigMaker' 1228 | wanted_hotkey = gsigmaker.Settings.hotkey 1229 | 1230 | def init(self): 1231 | global gsigmaker 1232 | if not gsigmaker: 1233 | gsigmaker = SigMakerPlugin() 1234 | return idaapi.PLUGIN_KEEP 1235 | 1236 | def run(self, arg=None): 1237 | global gsigmaker 1238 | if not gsigmaker: 1239 | gsigmaker = SigMakerPlugin() 1240 | gsigmaker.showGui() 1241 | 1242 | def term(self): 1243 | global gsigmaker 1244 | gsigmaker = None 1245 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pySigMaker 2 | 3 | An IDA Pro plugin to make creating code signatures quick and simple. This is a 4 | port of the popular (compiled) version of SigMaker-X64 with a new pyQt5 GUI that can 5 | be left open as a floating window or docked. The primary goal was to make a plugin 6 | that would work with many versions of IDA without needing to compile against the IDA SDK. 7 | Since the IDAPython API is now uniform in structure it seemed like a good time to tackle 8 | this project. 9 | 10 | Ported by: [zoomgod](https://www.unknowncheats.me/forum/members/146787.html) 11 | 12 | [Credits to the author/contributors of SigMaker-x64 for the core sig generating code.](https://github.com/ajkhoury/SigMaker-x64) 13 | 14 | ** Requires Python 3.5 or newer, tested with 3.8 ** 15 | 16 | IDA Pro version: Need feedback but oldest for sure would be IDA Pro 6.9. 17 | 18 | ### Install: 19 | copy pySigMaker.py into IDA plugin folder. 20 | 21 | **Note** Default is to use same hotkey as SigMaker-x64 (Ctrl-Alt-S) which will 22 | cause a warning to be displayed in IDA if orig SigMaker-x64 plugin 23 | exists. On settings tab there will be an option to archive the origional 24 | SigMaker-X64 plugins to a folder in IDA plugins dir. Or you can 25 | choose to change the hotkey. This plugin will get priority if both exist 26 | and default hotkey is used. 27 | 28 | ### Default hotkey: 29 | - Ctrl + Alt + S 30 | 31 | ### Feedback needed: 32 | Report IDA Pro, IDAPython and Python versions this worked on. 33 | 34 | ### Confirmed working: 35 | IDA Pro 7.0 36 | IDA Pro 7.5, IDAPython 64-bit v7.4.0 final, Python 3.8 64 bit 37 | IDA Pro 7.6 38 | 39 | ### Sig types supported: 40 | IDA Patterns : E8 ? ? ? ? 48 8B 83 ? ? ? ? 48 8B CB 41 | Olly Patterns: E8 ?? ?? ?? ?? 48 8B 83 ?? ?? ?? ?? 48 8B CB 42 | Code Patterns: \xE8\x00\x00\x00\x00\x48\x8B x????xx (mask optional) 43 | 44 | ### Bug fixes: 45 | Fixed issue with rebased images (related to ida inf struct) 46 | 47 | ### Not ported: 48 | 1. The crc signature portion was dropped. 49 | 50 | ### Changes: 51 | 1. Making a sig from selection was replaced with an auto-create at current address. 52 | 2. Trailing wildcards on sigs are dropped 53 | 3. Added a new pyQt5 tabbed Gui that can be used in floating or docked modes. 54 | 4. LOG_ERROR is minimum output setting now so error messages are always output. 55 | 5. Improved history, mask automatically restored based on sig selected in drop down. 56 | 57 | ### Images: 58 | 59 | ![Create Sigs Tab](https://i.imgur.com/dPWPn0Y.png) 60 | 61 | ![Test Sigs Tab](https://i.imgur.com/qsa4QmS.png) 62 | 63 | ![Settings Tab](https://i.imgur.com/Ngp7Sa5.png) 64 | 65 | ![Floating mode](https://i.imgur.com/QLggNlG.png) 66 | 67 | ![Docked](https://i.imgur.com/44BLrfS.png) 68 | --------------------------------------------------------------------------------