Skip to content

refactor of decoder, add resource parser #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 74 additions & 50 deletions trickbot/trick_config_decoder.py
Original file line number Diff line number Diff line change
@@ -1,101 +1,125 @@
#!/usr/bin/python2.7
#!/usr/bin/env python2.7
"Decodes AES encrypted modules of TrickBot"
# Crypto implementation taken from: https://github.com/kevthehermit/RATDecoders/blob/master/decoders/TrickBot.py
# resource module by dummys

__AUTHOR__ = 'hasherezade'

import argparse
import hashlib
from hashlib import sha256
from Crypto.Cipher import AES
from pefile import PE
from struct import unpack_from

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

def hash_rounds(data_buf):
while len(data_buf) <= 0x1000:
buf_hash = hashlib.sha256(data_buf).digest()
data_buf += buf_hash
return buf_hash
def derive_key(n_rounds, input_bf):
intermediate = input_bf
for i in range(0, n_rounds):
sha = sha256()
sha.update(intermediate)
current = sha.digest()
intermediate += current
return current


def aes_decrypt(data):
key = hash_rounds(data[:0x20])[:0x20]
iv = hash_rounds(data[0x10:0x30])[:0x10]
key = derive_key(128, data[:32])
iv = derive_key(128, data[16:48])[:16]
aes = AES.new(key, AES.MODE_CBC, iv)
data = pad(data[0x30:])
return aes.decrypt(data)

def find_pe(data):
while len(data):
mz_start = data.find('MZ')
if mz_start == -1:
return None
pe_start = data[mz_start:]
data = data[mz_start + len('MZ'):]
pe = data.find('PE')
if pe != -1:
return pe_start
return None
mod = len(data[48:]) % 16
if mod != 0:
data += '0' * (16 - mod)
return aes.decrypt(data[48:])[:-(16 - mod)]


def find_rsrc(pe):
"""Assumption is that the RSRC is called 'RES'
"""

for rsrc in pe.DIRECTORY_ENTRY_RESOURCE.entries:
for entry in rsrc.directory.entries:
if entry.name.string == "RES":
offset = entry.directory.entries[0].data.struct.OffsetToData
size = entry.directory.entries[0].data.struct.Size
return pe.get_memory_mapped_image()[offset:offset + size]
return 0


def dump_to_file(filename, data):
with open(filename, 'wb') as f:
f.write(data)


def dexor(data, key):
maxlen = len(data)
keylen = len(key)
j = 0 #key index
j = 0 # key index
decoded = ""
for i in range(0, maxlen):
kval = key[j % keylen]
decoded += chr(ord(data[i]) ^ ord(kval))
j += 1
return decoded


def main():
parser = argparse.ArgumentParser(description="TrickBot AES decoder")
parser.add_argument('--datafile',dest="datafile",default=None,help="File with data", required=True)
parser.add_argument('--botkey',dest="botkey",default=None,help="BotKey (SHA256)", required=False)
parser.add_argument('--outfile',dest="outfile",default=None, help="Where to dump the output", required=False)
parser.add_argument('--pe_name',dest="pe_name",default=None, help="Where to dump the PE", required=False)
group = parser.add_mutually_exclusive_group(required=True)

group.add_argument('--executable', dest="executable", default=None, help="Malware executable")
group.add_argument('--datafile', dest="datafile", default=None, help="encrypted module or config")
parser.add_argument('--botkey', dest="botkey", default=None, help="BotKey (SHA256)", required=False)
parser.add_argument('--outfile', dest="outfile", default=None, help="Where to dump the output", required=False)

args = parser.parse_args()
rsrc_mode = False
if args.executable:
pe = PE(args.executable)
data = find_rsrc(pe)
if not data:
# we did not found an encrypted config
print "Didn't found encrypted config"
return -1
rsrc_mode = True
data_len = unpack_from("<I",data)[0]
print "Length of the encrypted config file: %d" % data_len

data = open(args.datafile, 'rb').read()
data_len = len(data)
else:
# we are in datamode
data = open(args.datafile, 'rb').read()

if args.botkey is not None:
botkey = args.botkey.strip()
if len(botkey) == 64:
data = dexor(data, botkey)
if rsrc_mode:
data = dexor(data[4:], botkey)
else:
data = dexor(data, botkey)
else:
print "ERROR: Invalid BotKey: expected SHA256 hash"
return -1
else:
print "WARNING: in the new version of the TrickBot, BotKey (SHA256) is required for decoding modules"
data = data[4:]

output = aes_decrypt(data)
length = unpack_from('<I', output)[0]
output = output[8:length + 8]
print "Decoded: %d bytes" % len(output)

if output is None:
print "Output is empty"
return
return -1

if args.outfile is None:
args.outfile = args.datafile + ".out"
if rsrc_mode:
args.outfile = args.executable + ".out"
else:
args.outfile = args.datafile + ".out"

dump_to_file(args.outfile, output)
print "Dumped decoded to: %s" % (args.outfile)

pe_data = find_pe(output)
if pe_data is None:
return
print "Dumped decoded to: %s" % args.outfile

if args.pe_name is None:
args.pe_name = args.datafile + ".dll"

dump_to_file(args.pe_name, pe_data)
print "Extracted Module to: %s" % (args.pe_name)
return

if __name__ == '__main__':
main()