diff --git a/1768.json b/1768.json index 454f900..14b2432 100644 --- a/1768.json +++ b/1768.json @@ -1,72 +1,89 @@ { "dLookupValues": { - "LASTUPDATE": "2022/08/27", + "LASTUPDATE": "2023/04/02", "URL": "https://www.cobaltstrike.com/help-authorization-files", "37": { - "0": "trial or pirated? - Stats uniques -> ips/hostnames: 676 publickeys: 410", - "1": "Finspy - Stats uniques -> ips/hostnames: 26 publickeys: 19", - "100000": "Stats uniques -> ips/hostnames: 11 publickeys: 11", - "1011266395": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1061821957": "Stats uniques -> ips/hostnames: 4 publickeys: 1", - "1083092832": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "1200302529": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1225345476": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1234567890": "Stats uniques -> ips/hostnames: 178 publickeys: 152", - "1293900656": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1330515036": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "1359593325": "TrickBot/SmokeLoader/Nobelium/APT29 - Stats uniques -> ips/hostnames: 228 publickeys: 160", - "1360912112": "Stats uniques -> ips/hostnames: 3 publickeys: 3", - "1453642741": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1485646134": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "153163702": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1548680553": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1580103814": "APT27/Qbot/IcedID/DarkSide/Conti/Hancitor/WizardSpider - Stats uniques -> ips/hostnames: 80 publickeys: 34", - "1580103824": "Stats uniques -> ips/hostnames: 210 publickeys: 92", - "1616449647": "Stats uniques -> ips/hostnames: 3 publickeys: 2", - "1628610335": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "16777216": "Ryuk - Stats uniques -> ips/hostnames: 19 publickeys: 19", - "1711276032": "Stats uniques -> ips/hostnames: 18 publickeys: 15", - "1807886020": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "1857223080": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1873433027": "TA511/Hancitor - Stats uniques -> ips/hostnames: 35 publickeys: 28", - "1880445158": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1895490765": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "1914732777": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "2002705334": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "206546002": "Stats uniques -> ips/hostnames: 45 publickeys: 23", - "2091175951": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "289336829": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "294598720": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "305419776": "Stats uniques -> ips/hostnames: 49 publickeys: 19", - "305419896": "Ryuk/TrickBot/Maze/EvilCorp/Pyxie/APT41 - Stats uniques -> ips/hostnames: 269 publickeys: 172", - "388888888": "Stats uniques -> ips/hostnames: 5 publickeys: 5", - "401466503": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "426352781": "Stats uniques -> ips/hostnames: 267 publickeys: 207", - "472168751": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "475294171": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "508419252": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "540231004": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "555758901": "Stats uniques -> ips/hostnames: 3 publickeys: 2", - "571338205": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "582298219": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "6": "Stats uniques -> ips/hostnames: 13 publickeys: 12", - "666": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "666104495": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "666666": "Stats uniques -> ips/hostnames: 2 publickeys: 2", - "680943040": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "697620223": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "707557615": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "775423106": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "77771151": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "804449981": "Stats uniques -> ips/hostnames: 3 publickeys: 1", - "863200806": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "8848": "Stats uniques -> ips/hostnames: 8 publickeys: 8", - "9527": "Stats uniques -> ips/hostnames: 2 publickeys: 1", - "96906161": "Stats uniques -> ips/hostnames: 4 publickeys: 1", - "452436291": "REvil/Sodin/Sodinokibi - No stats", - "3": "Cobalt Group - No stats", - "849087011": "SolarStorm - No stats", - "892810033": "Teardrop/SolarStorm - No stats" + "0": "trial or pirated? - Stats uniques -> ips/hostnames: 740 publickeys: 448", + "1": "Finspy - Stats uniques -> ips/hostnames: 29 publickeys: 21", + "100000": "Stats uniques -> ips/hostnames: 49 publickeys: 41", + "1011266395": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1049482653": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "1061821957": "Stats uniques -> ips/hostnames: 4 publickeys: 1", + "1083092832": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "1116519211": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1200302529": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1225345476": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "12345": "Stats uniques -> ips/hostnames: 7 publickeys: 7", + "1234567890": "Stats uniques -> ips/hostnames: 253 publickeys: 197", + "1293900656": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1330515036": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "1335920331": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1359593325": "TrickBot/SmokeLoader/Nobelium/APT29 - Stats uniques -> ips/hostnames: 238 publickeys: 169", + "1360912112": "Stats uniques -> ips/hostnames: 3 publickeys: 3", + "1453642741": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1485646134": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "153163702": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1548680553": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1580103814": "APT27/Qbot/IcedID/DarkSide/Conti/Hancitor/WizardSpider - Stats uniques -> ips/hostnames: 80 publickeys: 34", + "1580103824": "Stats uniques -> ips/hostnames: 263 publickeys: 114", + "1616449647": "Stats uniques -> ips/hostnames: 3 publickeys: 2", + "1628610335": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "1670873463": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "16777216": "Ryuk - Stats uniques -> ips/hostnames: 19 publickeys: 19", + "1711276032": "Stats uniques -> ips/hostnames: 19 publickeys: 16", + "172432245": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "1807886020": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "1857223080": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1873433027": "TA511/Hancitor - Stats uniques -> ips/hostnames: 35 publickeys: 28", + "1880445158": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1895490765": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "1914732777": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "2002705334": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "206546002": "Stats uniques -> ips/hostnames: 80 publickeys: 33", + "2091175951": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "2130772225": "Stats uniques -> ips/hostnames: 3 publickeys: 3", + "289336829": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "294598720": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "305419776": "Stats uniques -> ips/hostnames: 62 publickeys: 20", + "305419896": "Ryuk/TrickBot/Maze/EvilCorp/Pyxie/APT41 - Stats uniques -> ips/hostnames: 284 publickeys: 179", + "3324337203": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "388888888": "Stats uniques -> ips/hostnames: 5 publickeys: 5", + "391144938": "Stats uniques -> ips/hostnames: 49 publickeys: 45", + "401466503": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "426352781": "Stats uniques -> ips/hostnames: 303 publickeys: 232", + "472168751": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "475294171": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "508419252": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "527324335": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "540231004": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "555758901": "Stats uniques -> ips/hostnames: 3 publickeys: 2", + "563527380": "Stats uniques -> ips/hostnames: 4 publickeys: 4", + "571338205": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "574247": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "582298219": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "6": "Stats uniques -> ips/hostnames: 19 publickeys: 18", + "666": "Stats uniques -> ips/hostnames: 2 publickeys: 2", + "666104495": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "666666": "Stats uniques -> ips/hostnames: 11 publickeys: 11", + "668694132": "Stats uniques -> ips/hostnames: 7 publickeys: 7", + "674054486": "Stats uniques -> ips/hostnames: 25 publickeys: 24", + "680943040": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "697620223": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "707557615": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "775423106": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "77771151": "Stats uniques -> ips/hostnames: 5 publickeys: 1", + "804449981": "Stats uniques -> ips/hostnames: 3 publickeys: 1", + "806236289": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "863200806": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "8848": "Stats uniques -> ips/hostnames: 8 publickeys: 8", + "921421590": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "9527": "Stats uniques -> ips/hostnames: 2 publickeys: 1", + "96906161": "Stats uniques -> ips/hostnames: 4 publickeys: 1", + "987654321": "Stats uniques -> ips/hostnames: 24 publickeys: 21", + "452436291": "REvil/Sodin/Sodinokibi - No stats", + "3": "Cobalt Group - No stats", + "849087011": "SolarStorm - No stats", + "892810033": "Teardrop/SolarStorm - No stats" }, "7": { "30819f300d06092a864886f70d010101050003818d003081890281810080fa8dc59ec39b73d49523c640c1cdfabbb0f0b15e943f2429c0c360862c938fb474523a0116f2ea71877f24218fc85cd959017cd0f987ec443a731a4d29a7a8fe1312d2edace8a736515d120c8f7b5e0008b7403ee3511435367f223c474ec2c0913c1dede6c1124b5089dc2aec3ef37ce24009a590ef4b8398f52e75c1f2ed020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": {"normal": "Has known private key", "verbose": "30820275020100300d06092a864886f70d01010105000482025f3082025b0201000281810080fa8dc59ec39b73d49523c640c1cdfabbb0f0b15e943f2429c0c360862c938fb474523a0116f2ea71877f24218fc85cd959017cd0f987ec443a731a4d29a7a8fe1312d2edace8a736515d120c8f7b5e0008b7403ee3511435367f223c474ec2c0913c1dede6c1124b5089dc2aec3ef37ce24009a590ef4b8398f52e75c1f2ed02030100010281800d789de81f1df515930584b8073976c7126577ae3edfa2fca6f3c0344baf4a363f35cb04cdea54b2d1eac207c70d9a72c02cc0b005af9a57be0490d3156e1d59ac7837c74987a296671f4264781050d39ccc16f5f5024699fe6f3aad9b77e874117e213b369ef6c58c43a4423585db42eb022251914f3110b52532620fe82dad024100f6c00bf6a8e14566029cfdfef39a77c50511056f235a0dc71b46c5d5b17b6494c290496a3d76635d5ae21f615bc5e04e2a2a2001957fec6b3ce88c0ca9a36aaf02410085d04e59788f32150b4039fc0140fa06a66181cb463cdec2d573111e8904fd4aadffa63e8f2f2063c7e212bef5981c1ed2ff6f1163a04662d483067658ceb32302403b44035b9a528935a83906f4be9402626b061c95061bb2257992b51fcf8240b54e4a13a815dd229ea09ea144e42311ee14488be9757c054ff8902e5b383f8cf702407fcd78d74926f2bd58968a0adf23b0e8a30623d2028e666f8d2fae1d0cdec010106947dd1e21f37c794eb97abad4019f8b043d8f4d28a9b100a8f78616c1ac2302403e247b8970b1e457b2c82759d3427a24edfa7d309a4a619ebfcab16cb2593a67564fe7c4d68b73958f856831e9fe1d4ba2ec4281234a2a903b4b07a50084b3d5"}, diff --git a/1768.py b/1768.py index c36e8b7..8966b0d 100644 --- a/1768.py +++ b/1768.py @@ -4,8 +4,8 @@ __description__ = 'Analyze Cobalt Strike beacons' __author__ = 'Didier Stevens' -__version__ = '0.0.16' -__date__ = '2022/08/20' +__version__ = '0.0.17' +__date__ = '2023/04/02' """ Source code put in the public domain by Didier Stevens, no Copyright @@ -56,6 +56,8 @@ 2022/07/31: 0.0.15 update class cAPIOptions 2022/08/17: added option --sanitycheck; refactored FinalTests 2022/08/20: 0.0.16 added output instructions to JSON output + 2022/08/30: 0.0.17 added option -x + 2023/04/02: updated man page Todo: @@ -105,6 +107,8 @@ def PrintManual(): This tool decrypts and dumps the configuration of Cobalt Strike Windows beacons (PE files), shellcode and memory dumps. +Use option -x to try all 256 xor keys for the configuration (not only 0x2e and 0x69). + Option -s (--select) can be used to select a particular configuration item (by decimal of hexadecimal number) for more information. For the moment, this option displays the complete item's data (hexadecimal in cleartext, encoded with 'i' (0x69) and encoded with '.' (0x2e). These hexadecimal values can be used to create detection rules, like YARA rules. Option -l (--licenseid) is used to generate YARA rules to detect a beacon or shellcode with the given license ID. The id can be provided as an integer or an hexadecimal value (prefixed by 0x). @@ -335,6 +339,7 @@ def PrintManual(): DEFAULT_SEPARATOR = ',' QUOTE = '"' +START_CONFIG = b'\x00\x01\x00\x01\x00\x02' START_CONFIG_I = b'ihihik' START_CONFIG_DOT = b'././.,' @@ -1481,8 +1486,14 @@ def InstantiateCOutput(options): filenameOption = options.output return cOutput(filenameOption) +class UnpackErrorNotEnoughData(Exception): + + pass + def Unpack(format, data): size = struct.calcsize(format) + if len(data) < size: + raise UnpackErrorNotEnoughData() result = list(struct.unpack(format, data[:size])) result.append(data[size:]) return result @@ -1641,8 +1652,10 @@ def LookupConfigValue(id, value): 0: 'windows-beacon_http-reverse_http', 1: 'windows-beacon_dns-reverse_http', 2: 'windows-beacon_smb-bind_pipz', + 4: 'windows-beacon_tcp-reverse_tcp', 8: 'windows-beacon_https-reverse_https', 16: 'windows-beacon_tcp-bind_tcp', + 32: 'to be determined', }, 0x0023: { 1: 'no proxy', @@ -1666,7 +1679,10 @@ def ConvertIntToIPv4(value): return ' %d.%d.%d.%d' % (C2IIP2(value[0]), C2IIP2(value[1]), C2IIP2(value[2]), C2IIP2(value[3])) def ToHexadecimal(value): - return binascii.b2a_hex(value).decode() + if isinstance(value, int): + return '%x' % value + else: + return binascii.b2a_hex(value).decode() def LookupValue(number, value, dInfo, verbose=False): lookup = '' @@ -1709,10 +1725,12 @@ def DetermineCSVersionFromConfig(dJSON): def SanityCheckExtractedConfig(dJSON): if not 1 in dJSON: return False - if not 8 in dJSON: + if not 7 in dJSON: return False if LookupConfigValue(1, dJSON[1]['rawvalue']) == '': return False + if not isinstance(dJSON[7]['rawvalue'], str): + return False if not dJSON[7]['rawvalue'].startswith('308'): return False return True @@ -1916,7 +1934,17 @@ def DecodeMalleableC2Instructions(parameter): def AnalyzeEmbeddedPEFileSub(payloadsectiondata, options): result = [] - dJSON = {} + + if options.xorkeys: + for xorKey in range(256): + xorKeyBytes = bytes([xorKey]) + startConfigXored = Xor(START_CONFIG, xorKeyBytes) + for position in FindAll(payloadsectiondata, startConfigXored): + result, dJSON = AnalyzeEmbeddedPEFileSub2(Xor(payloadsectiondata[position:position+0x10000], xorKeyBytes), result, options) + if result != [ERROR_SANITY_CHECK]: + return result, dJSON + return [result, {}] + xorKey = b'i' config, startconfig, endconfig = CutData(Xor(payloadsectiondata, xorKey), '[000100010002]:') if len(config) == 0: @@ -1930,15 +1958,21 @@ def AnalyzeEmbeddedPEFileSub(payloadsectiondata, options): startconfig, endconfig = StatisticalSearch(payloadsectiondata, xorKey) if startconfig == None: result.append(ERROR_NO_CONFIG) - return [result, dJSON] + return [result, {}] else: result.append('Config found (statistical): xorkey %s 0x%08x 0x%08x' % (xorKey, startconfig, endconfig)) result.append(cDump(Xor(payloadsectiondata[startconfig:endconfig + 1], xorKey)).HexAsciiDump(rle=True)) - return [result, dJSON] + return [result, {}] # result.append('Config found: 0x%08x 0x%08x %s' % (startconfig, endconfig, ' '.join(['0x%08x' % position for position in FindAll(payloadsectiondata, '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF')]))) # result.append('Config found: 0x%08x 0x%08x %s' % (startconfig, endconfig, ' '.join(['0x%08x' % position for position in FindAll(payloadsectiondata, '\x90\x01\x00\x00')]))) result.append('Config found: xorkey %s 0x%08x 0x%08x' % (xorKey, startconfig, endconfig)) data = config + + return AnalyzeEmbeddedPEFileSub2(data, result, options) + +def AnalyzeEmbeddedPEFileSub2(data, result, options): + dJSON = {} + dConfigIdentifiers = { 0x0001: 'payload type', 0x0002: 'port', @@ -2030,11 +2064,17 @@ def AnalyzeEmbeddedPEFileSub(payloadsectiondata, options): formatNumber = '>H' formatTypeLength = '>HH' ntlBytes = data[0:struct.calcsize(formatNumber) + struct.calcsize(formatTypeLength)] - number, data = Unpack(formatNumber, data) + try: + number, data = Unpack(formatNumber, data) + except UnpackErrorNotEnoughData: + break if number == 0: result.append('0x%04x' % number) break - type, length, data = Unpack(formatTypeLength, data) + try: + type, length, data = Unpack(formatTypeLength, data) + except UnpackErrorNotEnoughData: + break parameter, data = GetChunk(length, data) info = '' rawvalue = None @@ -2213,15 +2253,23 @@ def FinalTests(data, options, oOutput): # https://www.elastic.co/blog/detecting-cobalt-strike-with-memory-signatures 'Sleep mask 64-bit 4.2 deobfuscation routine': b'\x4C\x8B\x53\x08\x45\x8B\x0A\x45\x8B\x5A\x04\x4D\x8D\x52\x08\x45\x85\xC9\x75\x05\x45\x85\xDB\x74\x33\x45\x3B\xCB\x73\xE6\x49\x8B\xF9\x4C\x8B\x03', 'Sleep mask 32-bit 4.2 deobfuscation routine': b'\x8B\x46\x04\x8B\x08\x8B\x50\x04\x83\xC0\x08\x89\x55\x08\x89\x45\x0C\x85\xC9\x75\x04\x85\xD2\x74\x23\x3B\xCA\x73\xE6\x8B\x06\x8D\x3C\x08\x33\xD2', + 'Public key config entry': b'\x00\x07\x00\x03\x01\x00\x30\x81\x9F\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00\x03\x81\x8D\x00\x30\x81\x89\x02\x81', } for name, signature in dSignatures.items(): - for position in FindAll(data, signature): - oOutput.Line('%s found: 0x%08x' % (name, position)) - if options.verbose: - oOutput.Line(cDump(data[position-0x100:position], ' ', position-0x100).HexAsciiDump(rle=True), eol='') - oOutput.Line(' ... signature ...') - oOutput.Line(cDump(data[position+len(signature):position+len(signature)+0x100], ' ', position+len(signature)).HexAsciiDump(rle=True), eol='') + xorKeys = [b'\x00'] + if name == 'Public key config entry': + xorKeys = [b'\x00', b'\x2e', b'\x69'] + if options.xorkeys: + xorKeys = [bytes([iter]) for iter in range(256)] + for xorKey in xorKeys: + signatureXored = Xor(signature, xorKey) + for position in FindAll(data, signatureXored): + oOutput.Line('%s found: 0x%08x%s' % (name, position, IFF(xorKey == b'\x00', '', ' (xorKey %s)' % xorKey))) + if options.verbose: + oOutput.Line(cDump(data[position-0x100:position], ' ', position-0x100).HexAsciiDump(rle=True), eol='') + oOutput.Line(' ... signature ...') + oOutput.Line(cDump(data[position+len(signatureXored):position+len(signatureXored)+0x100], ' ', position+len(signatureXored)).HexAsciiDump(rle=True), eol='') #a# this is a kludge, to fix later when I have time def ProcessBinaryFileSub(sectiondata, data, oOutput, options): @@ -2239,7 +2287,7 @@ def ProcessBinaryFileSub(sectiondata, data, oOutput, options): if error != None: positionMZ = payload.find(b'MZ') if positionMZ != 0: - if START_CONFIG_I in sectiondata or START_CONFIG_DOT in sectiondata: + if START_CONFIG_I in sectiondata or START_CONFIG_DOT in sectiondata or options.xorkeys: AnalyzeEmbeddedPEFile(data, oOutput, options) elif TestShellcodeHeuristic(payload): if IdentifyShellcode(payload) == '': @@ -2251,6 +2299,7 @@ def ProcessBinaryFileSub(sectiondata, data, oOutput, options): elif positionMZ >= 0 and positionMZ < 0x20: oOutput.Line('MZ header found position %d' % positionMZ) AnalyzeEmbeddedPEFile(payload[positionMZ:], oOutput, options) + oOutput.Line(cDump(payload[:0x1000]).HexAsciiDump(rle=True)) elif len(payload) == 0: return False else: @@ -2319,6 +2368,7 @@ def ProcessBinaryFile(filename, content, cutexpression, flag, oOutput, oLogfile, bytesToSkip = 0x20 oOutput.Line('Skipping %d bytes' % bytesToSkip) ProcessBinaryFileSub(sectiondata[bytesToSkip:], data, oOutput, options) + FinalTests(data, options, oOutput) elif TestShellcodeHeuristic(data): if IdentifyShellcode(data) == '': oOutput.Line('Probably found shellcode:') @@ -2329,15 +2379,23 @@ def ProcessBinaryFile(filename, content, cutexpression, flag, oOutput, oLogfile, FinalTests(data, options, oOutput) else: dConfigs = {} - for position in FindAll(data, START_CONFIG_I) + FindAll(data, START_CONFIG_DOT): - result, dJSON = AnalyzeEmbeddedPEFileSub(data[position:position+0x10000], options) - configSha256 = hashlib.sha256(''.join(result).encode()).hexdigest() - if not configSha256 in dConfigs: - dConfigs[configSha256] = True - if result != [ERROR_SANITY_CHECK]: - oOutput.JSON(dJSON) - for line in result: - oOutput.Line(line) + if options.xorkeys: + xorKeys = range(256) + else: + xorKeys = [0x2E, 0x69] + for xorKey in xorKeys: + xorKeyBytes = bytes([xorKey]) + startConfigXored = Xor(START_CONFIG, xorKeyBytes) + for position in FindAll(data, startConfigXored): + result, dJSON = AnalyzeEmbeddedPEFileSub2(Xor(data[position:position+0x10000], xorKeyBytes), [], options) + configSha256 = hashlib.sha256(''.join(result).encode()).hexdigest() + if not configSha256 in dConfigs: + dConfigs[configSha256] = True + if result != [ERROR_SANITY_CHECK]: + oOutput.JSON(dJSON) + oOutput.Line('xorkey %s %02x' % (xorKeyBytes, xorKey)) + for line in result: + oOutput.Line(line) FinalTests(data, options, oOutput) # ---------------------------------------------- except: @@ -2478,6 +2536,7 @@ def __init__(self): self.verbose = False self.hash = False self.sanitycheck = False + self.xorkeys = False class cAPIOutput(object): def __init__(self): @@ -2539,6 +2598,7 @@ def Main(): oParser.add_option('-p', '--password', default='infected', help='The ZIP password to be used (default infected)') oParser.add_option('-n', '--noextraction', action='store_true', default=False, help='Do not extract from archive file') oParser.add_option('-H', '--hash', action='store_true', default=False, help='Include hashes of file content') + oParser.add_option('-x', '--xorkeys', action='store_true', default=False, help='Try all single byte XOR keys (not only 0x69 and 0x2e)') oParser.add_option('--literalfilenames', action='store_true', default=False, help='Do not interpret filenames') oParser.add_option('--recursedir', action='store_true', default=False, help='Recurse directories (wildcards and here files (@...) allowed)') oParser.add_option('--checkfilenames', action='store_true', default=False, help='Perform check if files exist prior to file processing') @@ -2568,7 +2628,10 @@ def Main(): PrintError(oExpandFilenameArguments.message) oLogfile.Line('Warning', repr(oExpandFilenameArguments.message)) + starttime = time.time() ProcessBinaryFiles(oExpandFilenameArguments.Filenames(), oLogfile, options) + if options.verbose: + print('Duration: %f' % (time.time() - starttime)) if oLogfile.errors > 0: PrintError('Number of errors: %d' % oLogfile.errors)