├── AUTHORS ├── CLA.md ├── License.txt ├── README.md ├── apihashesv2.bin ├── apihashesv2.py ├── apihashesv2_search └── __init__.py └── make_apihashesv2_table.py /AUTHORS: -------------------------------------------------------------------------------- 1 | Author: Igor Kuznetsov 2 | -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTOR LICENSE AGREEMENT 2 | You accept and agree to the following terms and conditions for Your past, present and future Contributions submitted to the AO Kaspersky Lab (“Company”). 3 | 1. Definitions. 4 | "You" (or "Your") shall mean the legal owner of the Contribution that is making this CLA with the Company. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 5 | "Contribution" shall mean any original work of authorship, including any derivative works, modifications or additions to an existing work, that is intentionally submitted by You to the Company for inclusion in, or documentation of, any of the products owned or distributed by the Company (the "Work"). You acknowledge that the Company desires to have all contributions made by You under the terms of this CLA and, thus, this CLA will apply to all of your Contributions submitted both before and after the date of signature. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Company or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Company for the purpose of discussing and improving the Work. 6 | 2. Grant of Copyright and Patent Licenses. 7 | Subject to the terms and conditions of this Agreement, You hereby grant to the Company and to recipients of software distributed by the Company a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license, including a license, to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, distribute, or sell Your Contributions and the Work. 8 | Subject to the terms and conditions of this Agreement, You hereby grant to the Company and to recipients of the Work distributed by the Company a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, use, offer to sell, sell, import, distribute, and otherwise transfer Your Contributions and the Work. 9 | 3. Ownership and third party rights. 10 | You represent that you are the sole legal owner and are legally entitled to grant the above license for the Contributions. If: 11 | o (A.) your employer(s) has intellectual property or other rights to your Contributions, you represent that you have both (i.) received express, prior, written permission to make Contributions on behalf of that employer; and (ii.) that your employer has waived any of its rights for or claims in your Contributions to the Company, or 12 | o (B.) if another individual or third party has rights to intellectual property to your Contributions – whether as a result of being a co-inventor, assignee, or other right, you represent that you have both (i.) received express, prior, written permission to make Contributions on behalf of that individual or third party; and (ii.) that such individual or third party has waived any of its rights for or claims in your Contributions to the Company. 13 | You will submit such written permission to the Company at the time of the submission of your Contribution. 14 | 4. Your original creation. 15 | You represent that each of Your Contributions is Your original creation (see section 5 for submissions on behalf of others). You represent that Your Contribution submissions include complete details (including required attributions and details of applicable license restrictions) of any third-party license or public domain licenses, or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware or should be aware and which are associated with any part of Your Contributions. 16 | 5. Third party owned creation(s). 17 | Should You wish to submit work that is not Your original creation, You may submit it to the Company separately from any Contribution, clearly identifying the complete details of its ownership and source, and any applicable license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work, for example: "Submitted on behalf of a third-party: [named here].” “Owned by third-party: [named here.]” or “Copyright held by third-party: [named here].” 18 | 6. Notification. 19 | You agree to notify the Company of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect, including if you become aware of any third party intellectual property rights that are infringed by your Contributions. 20 | 7. Assignment. 21 | Neither party may assign this Agreement without the other party’s consent which will not be unreasonably withheld; however, each party may assign this Agreement without the other party’s consent to an entity or individual that acquires all or substantially all of the business or assets of the assigning party or for an individual acquires all of the intellectual property rights in the Contribution owned by such individual, whether by merger, sale of assets, or otherwise, provided that such entity or individual assumes and agrees in writing to be bound by all of the obligations of the assigning party under this Agreement. 22 | 8. Entire agreement. 23 | This Agreement is the entire agreement, both written or oral, with respect to the Contributions between the parties. No amendment, modification or waiver of any provision of this Agreement will be effective unless in writing and signed by both parties. If any provision of this Agreement is held to be invalid or unenforceable, the remaining portions will remain in full force and effect and such provision will be enforced to the maximum extent possible so as to affect the intent of the parties and will be reformed to the extent necessary to make such provision valid and enforceable. All notices and other communications herein permitted or required under this Agreement will be sent by postage prepaid, via registered or certified mail or overnight courier, return receipt requested, or delivered personally to the parties at their respective addresses, or to such other address as either party will give to the other party in the manner provided herein for giving notice. Notice will be considered given upon receipt. 24 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | == 2 | 3 | © 2022 AO Kaspersky Lab. All Rights Reserved. 4 | 5 | == 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Apihashes v2 IDA plugin 2 | 3 | Apihashes is an IDA plugin that allows to automatically identify and mark known hash values for API function names. 4 | 5 | The plugin is implemented as a hook that checks the operands of new instructions and data items, and uses a database of pre-calculated hashes. 6 | 7 | The database is generated from a set of PE files using a script "make\_apihashesv2\_table.py". You can modify the script to add new hashing algorithms and non-standard DLLs. 8 | 9 | ## Installation 10 | 11 | Copy the files apihashesv2.py, apihashesv2.bin (the database) and the directory apihashesv2\_search into the %IDADIR%/plugins directory. The plugin should be loaded automatically when IDA starts. 12 | 13 | Dependencies: Python 3, pefile. 14 | 15 | ## Generating your own database 16 | 17 | If needed, modify make\_apihashesv2\_table.py to add the new hashing algoritm. Add the function to the *hashers* list. 18 | 19 | Run the script, providing the directories or filenames containing the target DLLs, for example the Windows "system32" directory. 20 | 21 | ``` 22 | python3 make_apihashesv2_table.py ...path_to_system32... 23 | ``` 24 | 25 | Processing will take some time, and as a result the script will generate a new file *apihashesv2.bin* in the current directory. Copy it to the %IDADIR%/plugins directory and reload IDA. 26 | 27 | -------------------------------------------------------------------------------- /apihashesv2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KasperskyLab/Apihashes/71905ff04655a0352614782ebcfb71c54435c656/apihashesv2.bin -------------------------------------------------------------------------------- /apihashesv2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # © 2022 AO Kaspersky Lab. All Rights Reserved. 3 | # 4 | # Installation: copy all the files and directories in the IDA's plugins/ directory 5 | 6 | import ida_idaapi, ida_idp, ida_ua, ida_bytes, ida_diskio, zlib, apihashesv2_search 7 | 8 | # Hook class 9 | class hk(ida_idp.IDB_Hooks): 10 | def CheckHash(self, ea, value): 11 | fname = apihashesv2_search.FindHash(value) 12 | if fname: 13 | print(f"[apihashes] {hex(ea)}: Found API hash for {fname}") 14 | ida_bytes.set_cmt(ea, fname, False) 15 | 16 | # This hook will check the operands of disassembled instructions 17 | # So, for older IDBs you may need to undefine and disassemble the code 18 | # again to make the plugin work 19 | def make_code(self, insn): 20 | for op in insn.ops: 21 | if op.type == ida_ua.o_void: 22 | break 23 | if op.type == ida_ua.o_imm and op.value != 0: 24 | self.CheckHash(insn.ea, op.value) 25 | 26 | return None 27 | 28 | # This hook will check the newly created data items (DWORDS, QWORDS) 29 | # So, for older IDBs you may need to undefine and recreate the data items 30 | # to force the checks 31 | def make_data(self, ea, flags, tid, sz): 32 | if sz == 4: 33 | opValue = ida_bytes.get_dword(ea) 34 | elif sz == 8: 35 | opValue = ida_bytes.get_qword(ea) 36 | else: 37 | return None 38 | self.CheckHash(ea, opValue) 39 | return None 40 | 41 | class apihashes_plugin_t(ida_idaapi.plugin_t): 42 | flags = 0 43 | comment = "Resolve API hashes on code/data creation" 44 | help = "No help" 45 | wanted_name = "Apihashes" 46 | wanted_hotkey = "" 47 | 48 | 49 | def init(self): 50 | self.hk = hk() 51 | self.hk.hook() 52 | # We're looking for the database in the IDA's plugins directory 53 | res = apihashesv2_search.LoadHashes(ida_diskio.idadir(ida_diskio.PLG_SUBDIR) + "/apihashesv2.bin") 54 | print(f"[apihashes] v2 plugin loaded, {res} hashes in the database") 55 | 56 | return ida_idaapi.PLUGIN_KEEP 57 | 58 | def run(self, arg): 59 | pass 60 | 61 | def term(self): 62 | pass 63 | 64 | 65 | def PLUGIN_ENTRY(): 66 | return apihashes_plugin_t() 67 | -------------------------------------------------------------------------------- /apihashesv2_search/__init__.py: -------------------------------------------------------------------------------- 1 | # © 2022 AO Kaspersky Lab. All Rights Reserved. 2 | 3 | from ctypes import * 4 | import zlib, bisect, struct 5 | 6 | # Array of the [hash, name offset] pairs 7 | tokens = None 8 | # The whole decompressed database 9 | decomp = None 10 | 11 | class HashRecBin(Structure): 12 | _fields_ = [ ('hash', c_uint64), ('off', c_uint64 ) ] 13 | 14 | def __lt__(self,other): 15 | return self.hash < other 16 | 17 | def LoadHashes(fileName): 18 | global tokens, decomp 19 | 20 | with open(fileName, "rb") as f: 21 | b = bytes(f.read()) 22 | decomp = zlib.decompress(b) 23 | # Read the header 24 | numItems = struct.unpack("= len(tokens): 34 | return None 35 | if tokens[i].hash != value: 36 | return None 37 | fname = "" 38 | for c in decomp[tokens[i].off:]: 39 | if c == 0: 40 | break 41 | fname = fname + chr(c) 42 | return fname 43 | 44 | def main(): 45 | print('Apihashes v2 test') 46 | LoadHashes("apihashesv2.bin") 47 | print(f'Hashes loaded, {len(tokens)} items') 48 | if FindHash(0x726774c) != 'LoadLibraryA': 49 | raise RuntimeError('Cannot find the hash for LoadLibraryA') 50 | if FindHash(0x6F721347) != 'RtlExitUserThread': 51 | raise RuntimeError('Cannot find the hash for RtlExitUserThread') 52 | if FindHash(0x6174A599) != 'connect': 53 | raise RuntimeError('Cannot find the hash for connect') 54 | print('All good.') 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /make_apihashesv2_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # © 2022 AO Kaspersky Lab. All Rights Reserved. 3 | # 4 | # Generate a table of pre-calculated API hashes for the IDA plugin 5 | 6 | import pefile, sys, struct, zlib 7 | import os, os.path 8 | 9 | # Helper routines for rotation 10 | def Ror32(val, howmuch): 11 | howmuch = howmuch % 32 12 | return ((val >> howmuch) | ( val<< (32-howmuch) )) & 0xFFFFFFFF 13 | 14 | def Rol32(val, howmuch): 15 | howmuch = howmuch % 32 16 | return ((val << howmuch) | ( val>> (32-howmuch) )) & 0xFFFFFFFF 17 | 18 | # Metasploit-style ROR 0xD hash (library name + api name) 19 | def Ror0D(name, libname): 20 | libhash = 0 21 | for c in libname: 22 | c = c - 0x20 if c >= 0x61 else c 23 | libhash = Ror32(libhash, 0xD) 24 | libhash += c 25 | libhash = Ror32(libhash, 0xD) 26 | # next one is zero 27 | 28 | libhash = Ror32(libhash, 0xD) 29 | libhash = Ror32(libhash, 0xD) 30 | 31 | res = 0 32 | for c in name: 33 | c = ord(c) 34 | res = Ror32(res, 0xD) 35 | res += c 36 | res = Ror32(res, 0xD) 37 | 38 | return (res + libhash) & 0xFFFFFFFF 39 | 40 | def ShadowHammer(name, libname): 41 | res = 0 42 | for c in name: 43 | c = ord(c) 44 | res = res * 0x83 45 | res += c 46 | 47 | return res & 0x7FFFFFFF 48 | 49 | # Raw ROR 0xD 50 | def Ror0D_Simple(name, libname): 51 | res = 0 52 | for c in name: 53 | c = ord(c) 54 | res = Ror32(res, 0xD) 55 | res += c 56 | return res & 0xFFFFFFFF 57 | 58 | def Djb2(name, libname): 59 | res = 0x1505 60 | for c in name: 61 | c = ord(c) 62 | res *= 0x21 63 | res += c 64 | 65 | return res & 0xFFFFFFFF 66 | 67 | def Adler32_DarkSide(name, libname): 68 | return zlib.adler32(name.encode('utf-8'), 0xFFFFFFFF) & 0xFFFFFFFF 69 | 70 | def Crc32(name, libname): 71 | return zlib.crc32(name.encode('utf-8')) & 0xFFFFFFFF 72 | 73 | # All hashing routines 74 | hashers = [Ror0D, ShadowHammer, Djb2, Adler32_DarkSide, Crc32, Ror0D_Simple] 75 | # hash:name dict 76 | hashtable = {} 77 | # unique name set for exported symbols 78 | allStrings = set() 79 | 80 | files_to_process = [] 81 | 82 | if len(sys.argv) < 2: 83 | print("Make the apihashesv2 binary database, for the IDA plugin") 84 | print("Usage: [dir with DLLs] [filename.dll] ...") 85 | exit() 86 | 87 | for f in sys.argv[1:]: 88 | if os.path.isfile(f): 89 | files_to_process.append(f) 90 | elif os.path.isdir(f): 91 | for dirname, _, filenames in os.walk(f): 92 | for fname in filenames: 93 | files_to_process.append(os.path.join(dirname, fname)) 94 | 95 | print(f"Processing {len(files_to_process)} files...") 96 | 97 | for f in files_to_process: 98 | try: 99 | pe = pefile.PE(f) 100 | except: 101 | print(f"Unable to load {f} as a PE file, skipping...") 102 | continue 103 | 104 | try: 105 | libname = pe.DIRECTORY_ENTRY_EXPORT.name 106 | for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: 107 | try: 108 | name = exp.name.decode('utf-8') 109 | for hasher in hashers: 110 | hashvalue = hasher(name, libname) 111 | hashtable[hashvalue] = name 112 | allStrings.add(name) 113 | # Debug point - you can print out the results of hashing 114 | print(f'{hex(hashvalue)} : {name}') 115 | except: 116 | pass 117 | except: 118 | pass 119 | 120 | # No names at all? Nothing to do here 121 | if len(hashtable) == 0: 122 | exit() 123 | 124 | # Now build a binary database 125 | # QWORD number of items 126 | # [2*QWORD]*number of items pairs of [hash,name offset], sorted by the hash value 127 | # rest of the file null-terminated symbol names 128 | stringLocations = {} 129 | 130 | headerSize = 8 + 16 * len(hashtable) 131 | 132 | stringbuf = b"" 133 | for string in sorted(allStrings): 134 | pos = len(stringbuf) + headerSize 135 | stringLocations[string] = pos 136 | stringbuf += string.encode('utf-8') + b'\x00' 137 | stringbuf += b'\x00' # Empty string as an ending mark 138 | 139 | output = b"" 140 | # Number of items 141 | output += struct.pack("