├── README.md ├── apiHashResolver.py ├── injectStructParser.py └── stringDecryption.py /README.md: -------------------------------------------------------------------------------- 1 | # QakbotTools 2 | Tools for assisting the reverse engineering of Qakbot's Web Inject Loader. 3 | 4 | May be effective against core Qakbot binary, due to reuse of encryption and hashing algorithms, though may need some repurposing regarding the queried registers and instructions. 5 | 6 | ## apiHashResolver.py 7 | 8 | - Takes three arguments: 9 | - The value used to XOR the API hashes with 10 | - The address of the string function used to decrypt DLL names 11 | - The address of API resolving functions 12 | - Will locate all cross references to the API resolving functions, parse out the required data, decrypt the DLL names, XOR the API hashes, and brute force them 13 | - Once the list of hashes have been resolved fully, they are then added to a new IDA local type, which is then assigned to the respective variable. 14 | 15 | ## injectStructParser.py 16 | 17 | - Takes one argument: 18 | - The address of the function that interacts with the inject structures 19 | - Will locate all cross references to the API resolving functions, parse out the required data, and decrypt the DLL names and API functions 20 | - Once the list has been parsed, comments will be added next to each entry in the inject structure, with the DLL and API name 21 | 22 | ## stringDecryption.py 23 | 24 | - Takes one to three arguments: 25 | ### Automated 26 | - The addresses of the string decryption functions 27 | ### Manual 28 | - The specific string decryption wrapper 29 | - The address where the wrapper is referenced 30 | - The correct string offset 31 | - Will locate all string decryption wrappers, parse the string and key data, and the string offset used for decryption 32 | - This data is then used to decrypt the correct string, which is added as a comment next to the string decryption wrapper call 33 | 34 | -------------------------------------------------------------------------------- /apiHashResolver.py: -------------------------------------------------------------------------------- 1 | import idaapi, idc, idautils, binascii, zlib, os, pefile 2 | 3 | 4 | def setHexRaysComment(stringToComment, functionAddress): 5 | 6 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 7 | 8 | cfunc = idaapi.decompile(functionAddress) 9 | tl = idaapi.treeloc_t() 10 | tl.ea = functionAddress 11 | tl.itp = idaapi.ITP_SEMI 12 | cfunc.set_user_cmt(tl, stringToComment) 13 | cfunc.save_user_cmts() 14 | 15 | def addStringComment(stringToComment, functionAddress): 16 | 17 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 18 | 19 | try: 20 | idc.set_cmt(functionAddress, stringToComment, 0) 21 | setHexRaysComment(stringToComment, functionAddress) 22 | except Exception as E: 23 | print ("Can't set comments at address %x." % functionAddress) 24 | 25 | return 26 | 27 | def readBytesFromFile(dataOffset, bytesToRead): 28 | 29 | return idaapi.get_bytes(dataOffset, bytesToRead) 30 | 31 | def extractListOfHashes(xorValue, hashListAddress, hashListSize): 32 | 33 | retrievedDwordList = readBytesFromFile(hashListAddress, hashListSize) 34 | 35 | retrievedDwordList = [((struct.unpack("I", retrievedDwordList[i:i+4])[0] ^ xorValue) & 0xffffffff) for i in range(0, len(retrievedDwordList), 4)] 36 | 37 | return retrievedDwordList 38 | 39 | 40 | def locateFunctionCrossReferences(functionAddress): 41 | 42 | return [addr.frm for addr in idautils.XrefsTo(functionAddress)] 43 | 44 | def decryptString(stringOffset, stringBlob, stringBlobSize, keyBlob): 45 | 46 | loopCounter = 0 47 | offsetStringEnd = stringOffset 48 | decryptedString = "" 49 | 50 | if stringOffset < stringBlobSize: 51 | 52 | while stringBlob[offsetStringEnd] != keyBlob[offsetStringEnd % 0x5A]: 53 | offsetStringEnd += 1 54 | stringBlobSize = offsetStringEnd - stringOffset 55 | 56 | while loopCounter <= stringBlobSize: 57 | 58 | decryptedByte = ord(stringBlob[(stringOffset + loopCounter)]) ^ ord(keyBlob[(stringOffset + loopCounter) % 0x5A]) 59 | decryptedString += chr(decryptedByte) 60 | 61 | loopCounter += 1 62 | 63 | return decryptedString 64 | 65 | def parseRequiredDLLExports(dllName): 66 | 67 | # https://github.com/phracker/HopperScripts/blob/master/list-pe-exports.py 68 | dllFileName = os.path.join("C:\\Windows\\System32", dllName.strip("\x00")) 69 | 70 | if not os.path.exists(dllFileName): 71 | print ("Failed to locate DLL %s." % dllFileName) 72 | return False 73 | 74 | pe = pefile.PE(dllFileName, fast_load=True) 75 | 76 | pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]]) 77 | return [dllExport.name for dllExport in pe.DIRECTORY_ENTRY_EXPORT.symbols] 78 | 79 | def bruteForceCRC32Hash(dllName, locatedAPIHash): 80 | 81 | resolvedAPI = None 82 | listOfExports = parseRequiredDLLExports(dllName) 83 | listOfExports = filter(None, listOfExports) 84 | for dllExport in listOfExports: 85 | 86 | if zlib.crc32(dllExport) & 0xFFFFFFFF == locatedAPIHash: 87 | resolvedAPI = dllExport 88 | break 89 | 90 | if resolvedAPI == None: 91 | resolvedAPI = "ErrorLoadingCorrectAPI::%X" % (locatedAPIHash) 92 | 93 | apiString = "%s::%s" % (dllName.replace(".", "_"), resolvedAPI) 94 | 95 | return apiString 96 | 97 | def retrieveStringFunctionArguments(functionCrossReference): 98 | 99 | specificStringOffset = 0 100 | stringBlobSize = 0 101 | keyBlobAddress = 0 102 | stringBlobAddress = 0 103 | 104 | currentAddress = functionCrossReference 105 | functionStart = idc.get_func_attr(functionCrossReference, FUNCATTR_START) 106 | 107 | previousAddress = idc.prev_head(currentAddress) 108 | 109 | stringBlobAddress = idc.get_operand_value(previousAddress, 1) 110 | 111 | previousAddress = idc.prev_head(previousAddress) 112 | 113 | if idc.print_insn_mnem(previousAddress) != "push": 114 | 115 | specificStringOffset = idc.get_operand_value(previousAddress, 1) 116 | previousAddress = idc.prev_head(previousAddress) 117 | 118 | stringBlobSize = idc.get_operand_value(previousAddress, 0) 119 | keyBlobAddress = idc.get_operand_value(idc.prev_head(previousAddress), 0) 120 | 121 | return stringBlobAddress, keyBlobAddress, stringBlobSize 122 | 123 | def retrieveAPIFunctionArguments(functionCrossReference): 124 | 125 | dwordPointer = 0 126 | dllStringOffset = 0 127 | hashListSize = 0 128 | hashDataAddress = 0 129 | 130 | currentAddress = functionCrossReference 131 | functionStart = idc.get_func_attr(functionCrossReference, FUNCATTR_START) 132 | functionEnd = idc.get_func_attr(functionCrossReference, FUNCATTR_END) 133 | 134 | while True: 135 | 136 | previousAddress = idc.prev_head(currentAddress) 137 | 138 | if previousAddress <= functionStart: 139 | break 140 | 141 | if idc.print_insn_mnem(previousAddress) == "push": 142 | if idc.get_operand_type(previousAddress, 0) == idc.o_imm and idc.is_off0(idc.get_full_flags(previousAddress)): 143 | hashDataAddress = idc.get_operand_value(previousAddress, 0) 144 | 145 | previousAddress = idc.prev_head(previousAddress) 146 | 147 | if idc.print_insn_mnem(previousAddress) == "push": 148 | if idc.get_operand_type(previousAddress, 0) == idc.o_imm and not idc.is_off0(idc.get_full_flags(previousAddress)): 149 | hashListSize = idc.get_operand_value(previousAddress, 0) 150 | 151 | previousAddress = idc.prev_head(previousAddress) 152 | 153 | if idc.print_insn_mnem(previousAddress) == "push": 154 | if idc.get_operand_type(previousAddress, 0) == idc.o_imm and not idc.is_off0(idc.get_full_flags(previousAddress)): 155 | dllStringOffset = idc.get_operand_value(previousAddress, 0) 156 | 157 | else: 158 | dllStringOffset = 0 159 | 160 | break 161 | 162 | currentAddress = previousAddress 163 | 164 | 165 | newAddress = functionCrossReference 166 | 167 | while True: 168 | 169 | nextAddress = idc.next_head(newAddress) 170 | 171 | if nextAddress >= functionEnd: 172 | break 173 | 174 | if idc.print_insn_mnem(nextAddress) == "mov" and idc.print_operand(nextAddress, 1) == "eax" and idc.is_off0(idc.get_full_flags(nextAddress)): 175 | 176 | dwordPointer = idc.get_operand_value(nextAddress, 0) 177 | 178 | break 179 | 180 | newAddress = nextAddress 181 | 182 | 183 | return hashDataAddress, hashListSize, dllStringOffset, dwordPointer 184 | 185 | def generateAPIStructure(dllName, resolvedAPIList): 186 | 187 | structureName = dllName + "_array" 188 | structID = idc.add_struc(-1, structureName, 0) 189 | 190 | for resolvedAPI in resolvedAPIList: 191 | idc.add_struc_member(structID, resolvedAPI, -1, FF_DWORD, -1, 4) 192 | 193 | return structureName 194 | 195 | def locateAPIFunctions(xorValue, internalStringFunction, listOfCoreFunctions): 196 | 197 | for coreFunction in listOfCoreFunctions: 198 | 199 | functionCrossReferences = locateFunctionCrossReferences(coreFunction) 200 | 201 | for functionReference in functionCrossReferences: 202 | 203 | resolvedAPIList = [] 204 | 205 | stringBlobAddress, keyBlobAddress, stringBlobSize = retrieveStringFunctionArguments(internalStringFunction) 206 | 207 | stringBlobData = readBytesFromFile(stringBlobAddress, stringBlobSize) 208 | keyBlobData = readBytesFromFile(keyBlobAddress, 0x5A) 209 | 210 | listOfHashes, hashListSize, dllStringOffset, dwordPointer = retrieveAPIFunctionArguments(functionReference) 211 | 212 | listOfConvertedHashes = extractListOfHashes(xorValue, listOfHashes, hashListSize) 213 | 214 | dllName = decryptString(dllStringOffset, stringBlobData, stringBlobSize, keyBlobData).strip("\x00") 215 | 216 | for convertedHash in listOfConvertedHashes: 217 | resolvedAPIString = bruteForceCRC32Hash(dllName, convertedHash) 218 | resolvedAPIList.append(resolvedAPIString) 219 | 220 | 221 | structureName = generateAPIStructure(dllName.replace(".", "_"), resolvedAPIList) 222 | 223 | idc.set_name(dwordPointer, structureName + "_ptr") 224 | idc.SetType(dwordPointer, structureName + "*") 225 | 226 | def apiAutomation(xorValue, internalStringFunction, *coreFunctionList): 227 | 228 | locateAPIFunctions(xorValue, internalStringFunction, coreFunctionList) 229 | -------------------------------------------------------------------------------- /injectStructParser.py: -------------------------------------------------------------------------------- 1 | import idaapi, idc, idautils, binascii, struct 2 | 3 | def setHexRaysComment(stringToComment, functionAddress): 4 | 5 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 6 | 7 | cfunc = idaapi.decompile(functionAddress) 8 | tl = idaapi.treeloc_t() 9 | tl.ea = functionAddress 10 | tl.itp = idaapi.ITP_SEMI 11 | cfunc.set_user_cmt(tl, stringToComment) 12 | cfunc.save_user_cmts() 13 | 14 | 15 | def addStringComment(stringToComment, functionAddress): 16 | 17 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 18 | 19 | try: 20 | idc.set_cmt(functionAddress, stringToComment, 0) 21 | setHexRaysComment(stringToComment, functionAddress) 22 | except Exception as E: 23 | print ("Can't set comments at address %x." % functionAddress) 24 | 25 | return 26 | 27 | def readBytesFromFile(dataOffset, bytesToRead): 28 | 29 | return idaapi.get_bytes(dataOffset, bytesToRead) 30 | 31 | def locateFunctionCrossReferences(functionAddress): 32 | 33 | return [addr.frm for addr in idautils.XrefsTo(functionAddress)] 34 | 35 | def decryptString(stringOffset, stringBlob, stringBlobSize, keyBlob): 36 | 37 | loopCounter = 0 38 | offsetStringEnd = stringOffset 39 | decryptedString = "" 40 | 41 | if stringOffset < stringBlobSize: 42 | 43 | while stringBlob[offsetStringEnd] != keyBlob[offsetStringEnd % 0x5A]: 44 | offsetStringEnd += 1 45 | stringBlobSize = offsetStringEnd - stringOffset 46 | 47 | while loopCounter <= stringBlobSize: 48 | 49 | decryptedByte = ord(stringBlob[(stringOffset + loopCounter)]) ^ ord(keyBlob[(stringOffset + loopCounter) % 0x5A]) 50 | decryptedString += chr(decryptedByte) 51 | 52 | loopCounter += 1 53 | 54 | return decryptedString 55 | 56 | def retrieveStringFunctionArguments(functionCrossReference): 57 | 58 | stringDecryptCall = 0 59 | stringBlobSize = 0 60 | keyBlobAddress = 0 61 | stringBlobAddress = 0 62 | 63 | newAddress = functionCrossReference 64 | functionEnd = idc.get_func_attr(functionCrossReference, FUNCATTR_END) 65 | 66 | while True: 67 | 68 | nextAddress = idc.next_head(newAddress) 69 | 70 | if nextAddress >= functionEnd: 71 | return stringBlobAddress, keyBlobAddress, stringBlobSize 72 | 73 | if idc.print_insn_mnem(nextAddress) == "call": 74 | stringDecryptCall = get_operand_value(nextAddress, 0) 75 | break 76 | 77 | newAddress = nextAddress 78 | 79 | 80 | currentAddress = stringDecryptCall 81 | functionEnd = idc.get_func_attr(currentAddress, FUNCATTR_END) 82 | 83 | keyBlobAddress = idc.get_operand_value(stringDecryptCall, 0) 84 | stringBlobSize = idc.get_operand_value(idc.next_head(stringDecryptCall), 0) 85 | stringBlobAddress = idc.get_operand_value(idc.next_head(idc.next_head(stringDecryptCall)), 1) 86 | 87 | return stringBlobAddress, keyBlobAddress, stringBlobSize 88 | 89 | 90 | def retrieveInjectStructFunctionArguments(functionCrossReference): 91 | 92 | pointerToStructure = 0 93 | structureItemCount = 0 94 | 95 | newAddress = functionCrossReference 96 | functionStart = idc.get_func_attr(functionCrossReference, FUNCATTR_START) 97 | 98 | while True: 99 | 100 | previousAddress = idc.prev_head(newAddress) 101 | 102 | if previousAddress < functionStart: 103 | break 104 | 105 | if idc.print_insn_mnem(previousAddress) == "mov" and idc.get_operand_type(previousAddress, 1) == idc.o_imm: 106 | pointerToStructure = idc.get_operand_value(previousAddress, 1) 107 | 108 | tempAddress = idc.prev_head(previousAddress) 109 | 110 | if idc.print_insn_mnem(tempAddress) == "inc": 111 | structureItemCount = 1 112 | break 113 | 114 | if idc.print_insn_mnem(previousAddress) == "push" and idc.get_operand_type(previousAddress, 0) == idc.o_imm: 115 | structureItemCount = idc.get_operand_value(previousAddress, 0) 116 | break 117 | 118 | newAddress = previousAddress 119 | 120 | return pointerToStructure, structureItemCount 121 | 122 | 123 | 124 | def locateInjectStructFunctions(listOfCoreFunctions): 125 | 126 | for coreFunction in listOfCoreFunctions: 127 | 128 | functionCrossReferences = locateFunctionCrossReferences(coreFunction) 129 | 130 | stringBlobAddress, keyBlobAddress, stringBlobSize = retrieveStringFunctionArguments(coreFunction) 131 | stringBlobData = readBytesFromFile(stringBlobAddress, stringBlobSize) 132 | keyBlobData = readBytesFromFile(keyBlobAddress, 0x5A) 133 | 134 | for functionReference in functionCrossReferences: 135 | 136 | pointerToStructure, structureItemCount = retrieveInjectStructFunctionArguments(functionReference) 137 | structureData = readBytesFromFile(pointerToStructure, structureItemCount * 21) 138 | 139 | splitStructures = [structureData[i:i + 21] for i in range(0, 21 * structureItemCount, 21)] 140 | 141 | for hookStructure in splitStructures: 142 | dllNameOffset = struct.unpack("I", hookStructure[0:4])[0] 143 | apiNameOffset = struct.unpack("I", hookStructure[4:8])[0] 144 | replaceOffset = struct.unpack("I", hookStructure[8:12])[0] 145 | originaOffset = struct.unpack("I", hookStructure[12:16])[0] 146 | 147 | decryptedDll = decryptString(dllNameOffset, stringBlobData, stringBlobSize, keyBlobData).strip("\x00") 148 | decryptedAPI = decryptString(apiNameOffset, stringBlobData, stringBlobSize, keyBlobData) 149 | 150 | print (decryptedDll + "::" + decryptedAPI) 151 | 152 | 153 | addStringComment(decryptedDll, pointerToStructure + (i * 21)) 154 | addStringComment(decryptedAPI, pointerToStructure + (i * 21) + 4) 155 | 156 | idc.set_name(replaceOffset, "replace" + decryptedAPI) 157 | idc.set_name(originaOffset, "original" + decryptedAPI) 158 | 159 | 160 | def injectStructAutomation(*coreFunctionList): 161 | 162 | locateInjectStructFunctions(coreFunctionList) 163 | -------------------------------------------------------------------------------- /stringDecryption.py: -------------------------------------------------------------------------------- 1 | import idaapi, idc, idautils, binascii 2 | 3 | def setHexRaysComment(stringToComment, functionAddress): 4 | 5 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 6 | 7 | cfunc = idaapi.decompile(functionAddress) 8 | tl = idaapi.treeloc_t() 9 | tl.ea = functionAddress 10 | tl.itp = idaapi.ITP_SEMI 11 | cfunc.set_user_cmt(tl, stringToComment) 12 | cfunc.save_user_cmts() 13 | 14 | 15 | def addStringComment(stringToComment, functionAddress): 16 | 17 | # https://gist.github.com/OALabs/04ef6b2d6203d162c5b3b0eefd49530c 18 | 19 | try: 20 | idc.set_cmt(functionAddress, stringToComment, 0) 21 | setHexRaysComment(stringToComment, functionAddress) 22 | except Exception as E: 23 | print ("Can't set comments at address %x." % functionAddress) 24 | 25 | return 26 | 27 | def readBytesFromFile(dataOffset, bytesToRead): 28 | 29 | return idaapi.get_bytes(dataOffset, bytesToRead) 30 | 31 | def locateFunctionCrossReferences(functionAddress): 32 | 33 | return [addr.frm for addr in idautils.XrefsTo(functionAddress)] 34 | 35 | def decryptString(stringOffset, stringBlob, stringBlobSize, keyBlob): 36 | 37 | loopCounter = 0 38 | offsetStringEnd = stringOffset 39 | decryptedString = "" 40 | 41 | if stringOffset < stringBlobSize: 42 | 43 | while stringBlob[offsetStringEnd] != keyBlob[offsetStringEnd % 0x5A]: 44 | offsetStringEnd += 1 45 | stringBlobSize = offsetStringEnd - stringOffset 46 | 47 | while loopCounter <= stringBlobSize: 48 | 49 | decryptedByte = ord(stringBlob[(stringOffset + loopCounter)]) ^ ord(keyBlob[(stringOffset + loopCounter) % 0x5A]) 50 | decryptedString += chr(decryptedByte) 51 | 52 | loopCounter += 1 53 | 54 | return decryptedString 55 | 56 | 57 | def locateStringOffset(functionAddress): 58 | 59 | stringOffset = 0 60 | 61 | previousAddress = functionAddress 62 | 63 | while True: 64 | 65 | previousAddress = idc.prev_head(previousAddress) 66 | 67 | if previousAddress <= functionAddress - 10: 68 | break 69 | 70 | if idc.print_insn_mnem(previousAddress) == "mov": 71 | if idc.get_operand_type(previousAddress, 0) == 1 and idc.get_operand_type(previousAddress, 1) == idc.o_imm: 72 | stringOffset = idc.get_operand_value(previousAddress, 1) 73 | return stringOffset 74 | 75 | return -1 76 | 77 | def retrieveFunctionArguments(functionCrossReference): 78 | 79 | specificStringOffset = 0 80 | stringBlobSize = 0 81 | keyBlobAddress = 0 82 | stringBlobAddress = 0 83 | 84 | currentAddress = functionCrossReference 85 | functionStart = idc.get_func_attr(functionCrossReference, FUNCATTR_START) 86 | 87 | previousAddress = idc.prev_head(currentAddress) 88 | 89 | stringBlobAddress = idc.get_operand_value(previousAddress, 1) 90 | 91 | previousAddress = idc.prev_head(previousAddress) 92 | 93 | if idc.print_insn_mnem(previousAddress) != "push": 94 | 95 | specificStringOffset = idc.get_operand_value(previousAddress, 1) 96 | previousAddress = idc.prev_head(previousAddress) 97 | 98 | stringBlobSize = idc.get_operand_value(previousAddress, 0) 99 | keyBlobAddress = idc.get_operand_value(idc.prev_head(previousAddress), 0) 100 | 101 | return stringBlobAddress, keyBlobAddress, stringBlobSize, specificStringOffset 102 | 103 | def locateStringFunctions(listOfCoreFunctions): 104 | 105 | for coreFunction in listOfCoreFunctions: 106 | 107 | functionCrossReferences = locateFunctionCrossReferences(coreFunction) 108 | 109 | for functionReference in functionCrossReferences: 110 | 111 | stringBlobAddress, keyBlobAddress, stringBlobSize, specificStringOffset = retrieveFunctionArguments(functionReference) 112 | 113 | stringBlobData = readBytesFromFile(stringBlobAddress, stringBlobSize) 114 | keyBlobData = readBytesFromFile(keyBlobAddress, 0x5A) 115 | 116 | if specificStringOffset != 0: 117 | decryptedString = decryptString(specificStringOffset, stringBlobData, stringBlobSize, keyBlobData) 118 | print (decryptedString) 119 | pass 120 | 121 | functionStart = idc.get_func_attr(functionReference, FUNCATTR_START) 122 | 123 | nextFunctionCrossReferences = locateFunctionCrossReferences(functionStart) 124 | 125 | for nextFunctionReference in nextFunctionCrossReferences: 126 | 127 | stringOffset = locateStringOffset(nextFunctionReference) 128 | 129 | if stringOffset == -1: 130 | continue 131 | 132 | decryptedString = decryptString(stringOffset, stringBlobData, stringBlobSize, keyBlobData) 133 | 134 | addStringComment(decryptedString, nextFunctionReference) 135 | 136 | def stringAutomation(*coreFunctionList): 137 | 138 | locateStringFunctions(coreFunctionList) 139 | 140 | 141 | def manualStringDecrypt(stringDecryptWrapper, referenceAddress, targetOffset): 142 | 143 | stringBlobAddress, keyBlobAddress, stringBlobSize,_ = retrieveFunctionArguments(stringDecryptWrapper) 144 | 145 | stringBlobData = readBytesFromFile(stringBlobAddress, stringBlobSize) 146 | keyBlobData = readBytesFromFile(keyBlobAddress, 0x5A) 147 | 148 | decryptedString = decryptString(targetOffset, stringBlobData, stringBlobSize, keyBlobData) 149 | 150 | addStringComment(decryptedString, referenceAddress) 151 | --------------------------------------------------------------------------------