Skip to content

Commit

Permalink
20211219a
Browse files Browse the repository at this point in the history
  • Loading branch information
DidierStevens committed Dec 19, 2021
1 parent efd93e1 commit 370df9f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 85 deletions.
40 changes: 24 additions & 16 deletions 1768.json
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
{
"dLookupValues": {
"LASTUPDATE": "2021/10/31",
"LASTUPDATE": "2021/12/12",
"URL": "https://www.cobaltstrike.com/help-authorization-files",
"37": {
"0": "trial or pirated? - Stats uniques -> ips/hostnames: 434 publickeys: 256",
"1": "Stats uniques -> ips/hostnames: 16 publickeys: 12",
"0": "trial or pirated? - Stats uniques -> ips/hostnames: 500 publickeys: 295",
"1": "Finspy - Stats uniques -> ips/hostnames: 20 publickeys: 14",
"1011266395": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"1225345476": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"1234567890": "Stats uniques -> ips/hostnames: 27 publickeys: 24",
"1234567890": "Stats uniques -> ips/hostnames: 54 publickeys: 50",
"1293900656": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"1359593325": "Stats uniques -> ips/hostnames: 157 publickeys: 112",
"1330515036": "Stats uniques -> ips/hostnames: 2 publickeys: 2",
"1359593325": "TrickBot/SmokeLoader/Nobelium/APT29 - Stats uniques -> ips/hostnames: 177 publickeys: 128",
"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": "Stats uniques -> ips/hostnames: 78 publickeys: 32",
"1580103824": "Stats uniques -> ips/hostnames: 53 publickeys: 16",
"1580103814": "APT27/Qbot/IcedID/DarkSide/Conti/Hancitor/WizardSpider - Stats uniques -> ips/hostnames: 79 publickeys: 33",
"1580103824": "Stats uniques -> ips/hostnames: 85 publickeys: 25",
"1616449647": "Stats uniques -> ips/hostnames: 3 publickeys: 2",
"16777216": "Stats uniques -> ips/hostnames: 17 publickeys: 17",
"1711276032": "Stats uniques -> ips/hostnames: 10 publickeys: 9",
"1628610335": "Stats uniques -> ips/hostnames: 2 publickeys: 2",
"16777216": "Ryuk - Stats uniques -> ips/hostnames: 17 publickeys: 17",
"1711276032": "Stats uniques -> ips/hostnames: 11 publickeys: 10",
"1807886020": "Stats uniques -> ips/hostnames: 3 publickeys: 1",
"1857223080": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"1873433027": "Stats uniques -> ips/hostnames: 26 publickeys: 20",
"1873433027": "TA511/Hancitor - Stats uniques -> ips/hostnames: 27 publickeys: 21",
"1914732777": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"289336829": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"294598720": "Stats uniques -> ips/hostnames: 3 publickeys: 1",
"305419776": "Stats uniques -> ips/hostnames: 23 publickeys: 12",
"305419896": "Stats uniques -> ips/hostnames: 175 publickeys: 113",
"305419776": "Stats uniques -> ips/hostnames: 28 publickeys: 17",
"305419896": "Ryuk/TrickBot/Maze/EvilCorp/Pyxie/APT41 - Stats uniques -> ips/hostnames: 194 publickeys: 124",
"388888888": "Stats uniques -> ips/hostnames: 5 publickeys: 5",
"401466503": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"426352781": "Stats uniques -> ips/hostnames: 130 publickeys: 110",
"426352781": "Stats uniques -> ips/hostnames: 174 publickeys: 142",
"472168751": "Stats uniques -> ips/hostnames: 3 publickeys: 1",
"475294171": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"508419252": "Stats uniques -> ips/hostnames: 3 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: 2 publickeys: 2",
"6": "Stats uniques -> ips/hostnames: 3 publickeys: 3",
"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",
"8848": "Stats uniques -> ips/hostnames: 2 publickeys: 2",
"863200806": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"8848": "Stats uniques -> ips/hostnames: 6 publickeys: 6",
"9527": "Stats uniques -> ips/hostnames: 2 publickeys: 1",
"96906161": "Stats uniques -> ips/hostnames: 4 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"
},
"7": {
"30819f300d06092a864886f70d010101050003818d003081890281810080fa8dc59ec39b73d49523c640c1cdfabbb0f0b15e943f2429c0c360862c938fb474523a0116f2ea71877f24218fc85cd959017cd0f987ec443a731a4d29a7a8fe1312d2edace8a736515d120c8f7b5e0008b7403ee3511435367f223c474ec2c0913c1dede6c1124b5089dc2aec3ef37ce24009a590ef4b8398f52e75c1f2ed020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": {"normal": "Has known private key", "verbose": "30820275020100300d06092a864886f70d01010105000482025f3082025b0201000281810080fa8dc59ec39b73d49523c640c1cdfabbb0f0b15e943f2429c0c360862c938fb474523a0116f2ea71877f24218fc85cd959017cd0f987ec443a731a4d29a7a8fe1312d2edace8a736515d120c8f7b5e0008b7403ee3511435367f223c474ec2c0913c1dede6c1124b5089dc2aec3ef37ce24009a590ef4b8398f52e75c1f2ed02030100010281800d789de81f1df515930584b8073976c7126577ae3edfa2fca6f3c0344baf4a363f35cb04cdea54b2d1eac207c70d9a72c02cc0b005af9a57be0490d3156e1d59ac7837c74987a296671f4264781050d39ccc16f5f5024699fe6f3aad9b77e874117e213b369ef6c58c43a4423585db42eb022251914f3110b52532620fe82dad024100f6c00bf6a8e14566029cfdfef39a77c50511056f235a0dc71b46c5d5b17b6494c290496a3d76635d5ae21f615bc5e04e2a2a2001957fec6b3ce88c0ca9a36aaf02410085d04e59788f32150b4039fc0140fa06a66181cb463cdec2d573111e8904fd4aadffa63e8f2f2063c7e212bef5981c1ed2ff6f1163a04662d483067658ceb32302403b44035b9a528935a83906f4be9402626b061c95061bb2257992b51fcf8240b54e4a13a815dd229ea09ea144e42311ee14488be9757c054ff8902e5b383f8cf702407fcd78d74926f2bd58968a0adf23b0e8a30623d2028e666f8d2fae1d0cdec010106947dd1e21f37c794eb97abad4019f8b043d8f4d28a9b100a8f78616c1ac2302403e247b8970b1e457b2c82759d3427a24edfa7d309a4a619ebfcab16cb2593a67564fe7c4d68b73958f856831e9fe1d4ba2ec4281234a2a903b4b07a50084b3d5"},
Expand Down
14 changes: 9 additions & 5 deletions 1768.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

__description__ = 'Analyze Cobalt Strike beacons'
__author__ = 'Didier Stevens'
__version__ = '0.0.10'
__date__ = '2021/11/17'
__version__ = '0.0.11'
__date__ = '2021/12/12'

"""
Source code put in the public domain by Didier Stevens, no Copyright
Expand Down Expand Up @@ -48,6 +48,7 @@
2021/11/07: added FinalTests
2021/11/14: added DNS fields
2021/11/17: added missing field names (ebook FINDING BEACONS IN THE DARK)
2021/12/12: 0.0.11 added 1768b.json support
Todo:
JSON output -> instructions
Expand Down Expand Up @@ -1657,10 +1658,13 @@ def DetermineCSVersionFromConfig(dJSON):
return ('4.4', maximumID)

def GetJSONData():
filename = os.path.join(GetScriptPath(), '1768b.json')
if os.path.isfile(filename):
return json.load(open(filename, 'r'))
filename = os.path.join(GetScriptPath(), '1768.json')
if not os.path.isfile(filename):
return {}
return json.load(open(filename, 'r'))
if os.path.isfile(filename):
return json.load(open(filename, 'r'))
return {}

class cStruct(object):
def __init__(self, data):
Expand Down
123 changes: 68 additions & 55 deletions cs-analyze-processdump.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

__description__ = 'Analyze Cobalt Strike beacon process dumps for further analysis'
__author__ = 'Didier Stevens'
__version__ = '0.0.2'
__date__ = '2021/11/09'
__version__ = '0.0.3'
__date__ = '2021/12/12'

"""
Source code put in the public domain by Didier Stevens, no Copyright
Expand All @@ -17,6 +17,7 @@
2021/11/02: continue
2021/11/03: continue
2021/11/09: 0.0.2 added summary and option -n
2021/12/12: 0.0.3 added options -r, --numberofkeystotry, --keystotry, --keysize
Todo:
Handle error when memory stream larger than segment?
Expand Down Expand Up @@ -63,6 +64,14 @@ def PrintManual():
Option -n can be used to disable writing of files.
If the input file is no a minidump, option -r can be used to process the input file as raw data.
Option --keysize specifies the size of the XOR key to recover (13 by default).
Option --numberofkeystotry specifies how many keys to list/process (10 by default).
Option --keystotry specifies the index of the keys to try (comma-separated list, 0 by default).
'''
for line in manual.split('\n'):
print(textwrap.fill(line, 79))
Expand Down Expand Up @@ -1309,46 +1318,45 @@ def ProcessBinaryFile(filename, content, dSummary, cutexpression, flag, oOutput,
if 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' in data:
oOutput.Line('sleep mask 32-bit 4.2 deobfuscation routine found!')

# listData = [['raw', data]]

listData = []
oMinidumpFile = MinidumpFile.parse_bytes(data)
oget_buffered_reader = oMinidumpFile.get_reader().get_buffered_reader()
for info in oMinidumpFile.memory_info.infos:
if info.Protect == MemoryInfoListStream.AllocationProtect.PAGE_READWRITE:
# print(info.Protect, info.BaseAddress, info.RegionSize, info.Type)
try:
oget_buffered_reader.move(info.BaseAddress)
except Exception as e:
if e.args[0].endswith(' is not in process memory space'):
print('Error: %s' % e.args[0])
continue
else:
raise e
try:
bytesSegment = oget_buffered_reader.read(info.RegionSize)
# print(len(bytesSegment))
listData.append([['segment', info], bytesSegment])
except OverflowError:
print('OverflowError') #a# handle overflow
except Exception as e:
if e.args[0] == 'Would read over segment boundaries!':
print('Error: %s' % e.args[0])
else:
raise e
listData = sorted(listData, reverse=True, key=lambda item: len(item[1]))
if options.raw:
listData = [[0, data]]
else:
listData = []
oMinidumpFile = MinidumpFile.parse_bytes(data)
oget_buffered_reader = oMinidumpFile.get_reader().get_buffered_reader()
for info in oMinidumpFile.memory_info.infos:
if info.Protect == MemoryInfoListStream.AllocationProtect.PAGE_READWRITE:
# print(info.Protect, info.BaseAddress, info.RegionSize, info.Type)
try:
oget_buffered_reader.move(info.BaseAddress)
except Exception as e:
if e.args[0].endswith(' is not in process memory space'):
print('Error: %s' % e.args[0])
continue
else:
raise e
try:
bytesSegment = oget_buffered_reader.read(info.RegionSize)
listData.append([['segment', info], bytesSegment])
except OverflowError:
print('OverflowError') #a# handle overflow
except Exception as e:
if e.args[0] == 'Would read over segment boundaries!':
print('Error: %s' % e.args[0])
else:
raise e
listData = sorted(listData, reverse=True, key=lambda item: len(item[1]))
listData = [[dataInfo[1].BaseAddress, data] for dataInfo, data in listData]

numberOfKeysToTry = 10
for dataInfo, data in listData:
oOutput.Line('Segment %x size %x' % (dataInfo[1].BaseAddress, len(data)))
for baseAddress, data in listData:
oOutput.Line('Segment %x size %x' % (baseAddress, len(data)))
dKeys = {}
keySize = 13
for offset in range(keySize):
for offset in range(options.keysize):
position = 0
while len(data[position + offset:position + offset + keySize]) == keySize:
key = data[position + offset:position + offset + keySize]
while len(data[position + offset:position + offset + options.keysize]) == options.keysize:
key = data[position + offset:position + offset + options.keysize]
dKeys[key] = dKeys.get(key, 0) + 1
position += keySize
position += options.keysize
oOutput.Line('Potential keys = %d' % len(dKeys))
keysSorted = []
normalizedKeys = set()
Expand All @@ -1362,28 +1370,29 @@ def ProcessBinaryFile(filename, content, dSummary, cutexpression, flag, oOutput,
if not normalizedKey in normalizedKeys:
keysSorted.append([key, value])
normalizedKeys.add(normalizedKey)
if len(keysSorted) >= numberOfKeysToTry:
if len(keysSorted) >= options.numberofkeystotry:
break

if len(keysSorted) > 0:
oOutput.Line('Probable keys:')
for index, (key, value) in enumerate(keysSorted[:numberOfKeysToTry]):
for index, (key, value) in enumerate(keysSorted[:options.numberofkeystotry]):
oOutput.Line('%d %d %s %s %f' % (index, value, key, binascii.b2a_hex(key), AverageDifferenceConsecutiveBytes(key)))

keyToTry = 0
oOutput.Line('Trying probable key %d:' % keyToTry)
for offset in range(keySize):
decrypted = Xor(data, keysSorted[keyToTry][0], offset)
if b'sha256\x00' in decrypted:
# print(dataInfo[1].Protect, dataInfo[1].BaseAddress, dataInfo[1].RegionSize, dataInfo[1].Type)
oOutput.Line('sha256\\x00 string found, key offset: %d' % offset)
dSummary['keys'] = dSummary.get('keys', []) + [binascii.b2a_hex(keysSorted[keyToTry][0])]
if not options.nowrite:
dumpFilename = '%s.%x-%d.bin' % (filename, dataInfo[1].BaseAddress, offset)
oOutput.Line('Writing segment to disk: %s' % dumpFilename)
dSummary['files'] = dSummary.get('files', []) + [dumpFilename]
with open(dumpFilename, 'wb') as fOut:
fOut.write(decrypted)
keysToTry = [int(index) for index in options.keystotry.split(',')]
for keyToTry in keysToTry:
oOutput.Line('Trying probable key %d:' % keyToTry)
for offset in range(options.keysize):
decrypted = Xor(data, keysSorted[keyToTry][0], offset)
if b'sha256\x00' in decrypted:
# print(dataInfo[1].Protect, dataInfo[1].BaseAddress, dataInfo[1].RegionSize, dataInfo[1].Type)
oOutput.Line('sha256\\x00 string found, key offset: %d' % offset)
dSummary['keys'] = dSummary.get('keys', []) + [binascii.b2a_hex(keysSorted[keyToTry][0])]
if not options.nowrite:
dumpFilename = '%s.%x-%d.bin' % (filename, baseAddress, offset)
oOutput.Line('Writing segment to disk: %s' % dumpFilename)
dSummary['files'] = dSummary.get('files', []) + [dumpFilename]
with open(dumpFilename, 'wb') as fOut:
fOut.write(decrypted)

oOutput.Line('')

Expand Down Expand Up @@ -1444,10 +1453,14 @@ def Main():
oParser.add_option('-m', '--man', action='store_true', default=False, help='Print manual')
oParser.add_option('-o', '--output', type=str, default='', help='Output to file (# supported)')
oParser.add_option('-n', '--nowrite', action='store_true', default=False, help='Do not write decoded segments to disk')
oParser.add_option('-r', '--raw', action='store_true', default=False, help='Assume the input file is raw data')
oParser.add_option('--numberofkeystotry', type=int, default=10, help='Number of keys to try (default 10)')
oParser.add_option('--keystotry', type=str, default='0', help='Keys (index) to try (default 0)')
oParser.add_option('--keysize', type=int, default=13, help='Size of XOR key (default 13)')
oParser.add_option('-p', '--password', default='infected', help='The ZIP password to be used (default infected)')
oParser.add_option('--noextraction', action='store_true', default=False, help='Do not extract from archive file')
oParser.add_option('-l', '--literalfilenames', action='store_true', default=False, help='Do not interpret filenames')
oParser.add_option('-r', '--recursedir', action='store_true', default=False, help='Recurse directories (wildcards and here files (@...) allowed)')
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')
oParser.add_option('-j', '--jsoninput', action='store_true', default=False, help='Consume JSON from stdin')
oParser.add_option('--logfile', type=str, default='', help='Create logfile with given keyword')
Expand Down
Loading

0 comments on commit 370df9f

Please sign in to comment.