This commit is contained in:
blackorbird
2019-04-08 15:46:31 +08:00
parent 81a71cac01
commit 105c56463c
13 changed files with 1281 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
IOC
549726b8bfb1919a343ac764d48fdc81
SedUploader payload, compiled on 2018-11-21
ebdc6098c733b23e99daa60e55cf858b
SedUploader payload, compiled on 2018-12-07
70213367847c201f65fed99dbe7545d2
SedUploader payload, compiled on 2018-12-07
c4601c1aa03d83ec11333d7400a2bbaf
SedUploader payload, compiled on 2019-01-28
a13c864980159cd9bdc94074b2389dda
Zebrocy downloader type 1 (.NET), compiled on 2018-11-13
f05a7cc3656c9467d38d54e037c24391
Zebrocy downloader type 1 (Delphi), VT 1st seen on 2018-11-06
7e67122d3a052e4755b02965e2e56a2e

12
APT28/REALTED_REPORT.md Normal file
View File

@@ -0,0 +1,12 @@
https://www.welivesecurity.com/wp-content/uploads/2016/10/eset-sednit-full.pdf
https://www.welivesecurity.com/2017/05/09/sednit-adds-two-zero-day-exploits-using-trumps-attack-syria-decoy/
https://www.emanueledelucia.net/apt28-targeting-military-institutions/
https://www.emanueledelucia.net/apt28-sofacy-seduploader-under-the-christmas-tree/
https://unit42.paloaltonetworks.com/unit42-sofacy-continues-global-attacks-wheels-new-cannon-trojan/
https://unit42.paloaltonetworks.com/dear-joohn-sofacy-groups-global-campaign/
https://unit42.paloaltonetworks.com/sofacy-creates-new-go-variant-of-zebrocy-tool/
https://blog.trendmicro.co.jp/archives/19829
https://www.welivesecurity.com/2018/11/20/sednit-whats-going-zebrocy/
https://twitter.com/DrunkBinary
https://github.com/williballenthin/idawilli/blob/master/scripts/yara_fn/yara_fn.py
https://twitter.com/r0ny_123

View File

@@ -0,0 +1,168 @@
# 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("<Q", hexdec[:8])[0] <= len(hexdec[8:]) and len(hexdec[8:]) % 0x10 == 0:
size = unpack("<Q", hexdec[:8])[0]
dprint('plain text size = {:#x}'.format(size))
enc = hexdec[8:]
#try:
dec = aes.decrypt(enc)
#except ValueError:
#return ''
dprint('AES-decrypted with null bytes = {}'.format(repr(dec)))
plain = dec[:size]
dprint('AES-decrypted plain text = {}'.format(plain))
return plain
else:
#dprint('plain text size {:#x} is larger than encrypted data size {:#x}. this string is not encrypted'.format(size, len(hexdec[8:])))
if len(hexdec) < g_MAX_HEXTEXT_SIZE:
success('decoded hextext: {}'.format(hexdec))
return hexdec
else:
return ''
def parse(buf, post=False):
dprint('now parsing: {}'.format(buf))
if post:
success('AES-decrypted POST file content: {}'.format(buf))
b64enc, txt = buf.split(g_delimiter_post)
b64dec = base64.decodestring(b64enc)
success('base64 decoded = {}'.format(b64dec))
vid = txt.split('-')[0]
hexdec = decode(vid, 0)
vsn = unpack("<I", hexdec[:4])[0]
phn = hexdec[4:]
success('victim ID = {} (VolumeSerialNumber = {:#x}, part of hostname = {})'.format(vid, vsn, phn))
else:
hexdec = decode(buf, 0)
dprint('hextext to bin #2: {}'.format(repr(hexdec)))
hexparams = hexdec.split(g_delimiter_conf)
try:
params = [decode(x, 0) for x in hexparams]
except (ValueError, IndexError):
params = hexparams
success('{}'.format(params))
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-k', '--key', default=g_aes_key, help="AES key")
parser.add_argument('-c', '--choose', action='store_true', help="choose AES key from decoded strings")
parser.add_argument('-t', '--text', help="encrypted hextext")
parser.add_argument('-p', '--post', action='store_true', help="the text is HTTP POST file content")
parser.add_argument('-f', '--file', help="binary file with encrypted hextexts")
parser.add_argument('--debug', '-d', action='store_true', help="print debug output")
parser.add_argument('--uni', '-u', action='store_true', help="unicode hextext mode")
parser.add_argument('--strict', '-s', action='store_true', help="strict hextext mode")
args = parser.parse_args()
global g_debug
g_debug = args.debug
info('start')
aes = AES.new(args.key[:0x20], AES.MODE_ECB)
if args.text:
plain = decrypt_hextext(args.text, aes)
parse(plain, args.post)
if args.file:
with open(args.file, 'rb') as f:
data = f.read()
stored = '' # for divided hextext
if args.uni:
for s,p in extract_unicode(g_pat_hexunicode, data):
dprint('{:#x}: hextext found'.format(p))
plain = decrypt_hextext(s, None, 1)
else:
''' # for AES decryption of payload strings
if args.choose: # for payload 10.3
for s,p in extract_ascii(g_pat_hexasciidummy, data):
dprint('{:#x}: possible hexkey with dummy small chars found (payload v10.3)'.format(p))
hexkey = extract_hexkey(s)
if len(hexkey) == g_AES_KEY_SIZE * 2:
key = decode(hexkey)
success('possible AES key acquired: {}'.format(key))
aes = AES.new(key[:0x20], AES.MODE_ECB)
'''
pat = g_pat_hexascii_go if args.strict else g_pat_hexascii
for s,p in extract_ascii(pat, data):
dprint('{:#x}: hextext found {}'.format(p, s))
s = re.sub(r"[#\-=@%$]", "", s) # delete dummy characters
plain = decrypt_hextext(s, aes)
dprint('len(s)={:#x}, len(plain)={:#x}'.format(len(s), len(plain)))
''' # for AES decryption of payload strings
if len(s) > 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()