# zebrocy_decrypt_artifact.py - script to decode Zebrocy downloader hex strings # Takahiro Haruyama (@cci_forensics) # Note: the script was used to decode and AES-decrypt C2 traffic data generated by Zebrocy payload # I've not seen Zebrocy payload lately (2019/1Q), so commented out the code import argparse, base64, re from Crypto.Cipher import AES from struct import * g_debug = False g_delimiter_post = ':' g_delimiter_conf = '\r\n' g_AES_KEY_SIZE = 38 #g_pat_hexascii = re.compile(r'[0-9A-F]{6,}') g_pat_hexascii = re.compile(r'[0-9A-F#\-=@%$]{6,}') # downloader type1 (Delphi) g_pat_hexascii_go = re.compile(r'(?:[2-7][0-9A-F]){2,}') # downloader type1 (Go) g_pat_hexunicode = re.compile(ur'(?:[0-9A-F][\x00]){2,}') # downloader type2 (Delphi) #g_pat_ascii = re.compile(r'[\x20-\x7E]{3,}') g_pat_hexasciidummy = re.compile(r'[0-9A-Fa-z]{76,150}') # hexascii with dummy small alphabet for payload v10.3 g_MAX_HEXTEXT_SIZE = 0x200 g_aes_key = 'DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMY' def info(msg): print "[*] {}".format(msg) def success(msg): print "[+] {}".format(msg) def error(msg): print "[!] {}".format(msg) def dprint(msg): if g_debug: print "[DEBUG] {}".format(msg) def decode(buf, adjust): newbuf = [] for i in range(0, len(buf), 2): if buf[i] and buf[i+1]: newbuf.append(chr(int(buf[i] + buf[i+1], 16) + adjust)) return "".join(newbuf) def extract_ascii(pat, data): for match in pat.finditer(data): yield match.group().decode("ascii"), match.start() def extract_unicode(pat, data): for match in pat.finditer(data): yield match.group().decode("utf-16le"), match.start() def extract_hexkey(s): hexkey = [x for x in s if ord(x) < ord('Z')] return ''.join(hexkey) def decrypt_hextext(hexenc, aes=None, adjust=0): try: hexdec = decode(hexenc, adjust) except (ValueError, IndexError): return '' dprint('hextext to bin: {}'.format(repr(hexdec))) if aes and len(hexdec) > 8 and unpack(" g_MAX_HEXTEXT_SIZE and plain == '': dprint('{:#x}: possible divided config block'.format(p)) stored += s plain = decrypt_hextext(stored, aes) if plain != '': stored = '' if args.choose and len(plain) == g_AES_KEY_SIZE: success('possible AES key acquired: {}'.format(plain)) aes = AES.new(plain[:0x20], AES.MODE_ECB) if g_pat_hexascii.match(plain) and len(plain) % 2 == 0: parse(plain) ''' info('done') if __name__ == '__main__': main()