11
This commit is contained in:
20
APT28/IOC/2019-04-05-ioc-mark.txt
Normal file
20
APT28/IOC/2019-04-05-ioc-mark.txt
Normal 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
12
APT28/REALTED_REPORT.md
Normal 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
|
||||
168
APT28/decode/zebrocy_decrypt_artifact.py
Normal file
168
APT28/decode/zebrocy_decrypt_artifact.py
Normal 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()
|
||||
Reference in New Issue
Block a user