Dridex (DoppelDridex) Loader
Analysis of the DoppelDridex loader
- Overview
- Helper Functions
- Known Data (Config) From Joe Sandbox
- RC4 Decryption
- Test Data Decrypt
- Shellcode Analysis
- Test String Decryption
- C2 Networking
def unhex(hex_string):
import binascii
if type(hex_string) == str:
return binascii.unhexlify(hex_string.encode('utf-8'))
else:
return binascii.unhexlify(hex_string)
def tohex(data):
import binascii
if type(data) == str:
return binascii.hexlify(data.encode('utf-8'))
else:
return binascii.hexlify(data)
def rc4crypt(data, key):
#If the input is a string convert to byte arrays
if type(data) == str:
data = data.encode('utf-8')
if type(key) == str:
key = key.encode('utf-8')
x = 0
box = list(range(256))
for i in range(256):
x = (x + box[i] + key[i % len(key)]) % 256
box[i], box[x] = box[x], box[i]
x = 0
y = 0
out = []
for c in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
out.append(c ^ box[(box[x] + box[y]) % 256])
return bytes(out)
APLib
Credit: Sandor Nemes (snemes)
import struct
from binascii import crc32
from io import BytesIO
__all__ = ['APLib', 'decompress']
__version__ = '0.6'
__author__ = 'Sandor Nemes'
class APLib(object):
__slots__ = 'source', 'destination', 'tag', 'bitcount', 'strict'
def __init__(self, source, strict=True):
self.source = BytesIO(source)
self.destination = bytearray()
self.tag = 0
self.bitcount = 0
self.strict = bool(strict)
def getbit(self):
# check if tag is empty
self.bitcount -= 1
if self.bitcount < 0:
# load next tag
self.tag = ord(self.source.read(1))
self.bitcount = 7
# shift bit out of tag
bit = self.tag >> 7 & 1
self.tag <<= 1
return bit
def getgamma(self):
result = 1
# input gamma2-encoded bits
while True:
result = (result << 1) + self.getbit()
if not self.getbit():
break
return result
def depack(self):
r0 = -1
lwm = 0
done = False
try:
# first byte verbatim
self.destination += self.source.read(1)
# main decompression loop
while not done:
if self.getbit():
if self.getbit():
if self.getbit():
offs = 0
for _ in range(4):
offs = (offs << 1) + self.getbit()
if offs:
self.destination.append(self.destination[-offs])
else:
self.destination.append(0)
lwm = 0
else:
offs = ord(self.source.read(1))
length = 2 + (offs & 1)
offs >>= 1
if offs:
for _ in range(length):
self.destination.append(self.destination[-offs])
else:
done = True
r0 = offs
lwm = 1
else:
offs = self.getgamma()
if lwm == 0 and offs == 2:
offs = r0
length = self.getgamma()
for _ in range(length):
self.destination.append(self.destination[-offs])
else:
if lwm == 0:
offs -= 3
else:
offs -= 2
offs <<= 8
offs += ord(self.source.read(1))
length = self.getgamma()
if offs >= 32000:
length += 1
if offs >= 1280:
length += 1
if offs < 128:
length += 2
for _ in range(length):
self.destination.append(self.destination[-offs])
r0 = offs
lwm = 1
else:
self.destination += self.source.read(1)
lwm = 0
except (TypeError, IndexError):
if self.strict:
raise RuntimeError('aPLib decompression error')
return bytes(self.destination)
def pack(self):
raise NotImplementedError
def aplib_decompress(data, strict=False):
packed_size = None
packed_crc = None
orig_size = None
orig_crc = None
if data.startswith(b'AP32') and len(data) >= 24:
# data has an aPLib header
header_size, packed_size, packed_crc, orig_size, orig_crc = struct.unpack_from('=IIIII', data, 4)
data = data[header_size : header_size + packed_size]
if strict:
if packed_size is not None and packed_size != len(data):
raise RuntimeError('Packed data size is incorrect')
if packed_crc is not None and packed_crc != crc32(data):
raise RuntimeError('Packed data checksum is incorrect')
result = APLib(data, strict=strict).depack()
if strict:
if orig_size is not None and orig_size != len(result):
raise RuntimeError('Unpacked data size is incorrect')
if orig_crc is not None and orig_crc != crc32(result):
raise RuntimeError('Unpacked data checksum is incorrect')
return result
data = unhex('991fd7128ac6c8165a9d2daf172ef27944fd14f1fcaed3ff7f8cc4e02e434d439ee2becd4f076605a7bb5e20a2f4a73fa6c59f94cd7729eaa202576c1d33f0fc725fcdccd2b1d4dfb60c11f8d68aa7cc7acd78f97a9f9d777508d3d53c9c5fe6c64bb834f97680548dc6f093328097283261926ff58e662476ec8689b0b136ce9d7a8aa27a884c559fd5a60358f54c150587b89b01d6a3abdb2ec419029df02ce69c9997821cb14c8b2a4bbd74b747921650b98c93b50db1e557aa258b31397fe4ae9fde0f042dc856a2ed2865ccc639820ae7e94042f5deaa9c2d41f6a3034a251d954642e76b48002a2479ad96dea13bb77e74b1c28fbf5c61da648def0b3e99eda2a396594e3f59b3bc5d22c39f7aacc792a4e3db0bacdc5623dc2a333b8de3f27f3075ad2fd2a118b903e1f1f5fed45bde3fc1df0047f05ab27f0617a690440bba697ced12a1003eba57e49b69d40b9e4cbaf640ea8f03a57d2dc3507377ac4732a2eac76a10fc562139b3cc3cf616641feaeae1d580a4092f9ba3dbb0296ac8c7669d5c3635e2b2de5e05c2b2090c470b842be348f8aa35cac0b5a216d089689d88cb4f84d4a399c31d6acdb1bc7f86094fade26c3070bda7a37ae3254f94170f6163e8bef596c9025975d67bdcd1bf35cfdbed3ce789785f6d3c71b0472514dd2672c496d0fff1f5609ad632120c243930e481732ec5791ef33d0e7de20f925dc21f7aeeaea6a08af88ba3ae57863209f07d860cc0a54910043566ad4bdb4287a0fccce20685ec03f350a2fd22d7aaffb17304b5b7466fe317f624d91f53bf8d4d96369c810c0beae24c9307d555963ef0b3fd90fa1e7c99475a4b2c19047b356ca0e1220be20ba501f6e9c7e93a7803066d570be2d9f246a07ac151ec78ed665ab3ca827e6d03fe941b1ccc6e0d1a146c03c5751fe38e12781d34c90bf7636f9e5e322af63ba2e4fe71f6a2dffc05db028d5e3ac27a9a823c3c4471063f41a12263dd6378d32b602b6900d2de732ff215390ac8392fc6309f521e6a35d419dd8eb6a3be617fb48fc1ccbe1d640736b8fec3065a66b119fb7bdbf192bd9efd25c806a5cbb47d2fbcf3f5d2bd6a30a36617cc19bea06730e85417d006a44d35072fdb24dcb358c94b898a2fa80ab797113b636af50350a20cfb54ce441b15d26e62745354c223d342654e534a929ff5140b8bcba469e24794d795065ba8734b84d2063ae762d112fe5415266bab8ae35d10d68df561631acca2e69e61704bc4ee498c1c276c56251d3c7e5314e043bc6281d644e5e38d9fad56cb3284f2d2819b42423bd914bb7e75e46f47fce8f6da23c451d244bb807a5ce4399158f036b9364f6a91022ccc34dea34a2c00a0003c3fb773061c060a426c1ecd5aadbcaaccbe0146057cde291bf5f2968a58bb8d2a3793aa0431b6a5b20357eef157c0ed2340925c6ec1eb083d1edb3fe4fe72c2b9660507dee987cd5b612c5bf1048149c609feb0b658766af44f6368c6fd8ab2be56c4b1b102006681ab2423d9b47b6cda3445f7209c3b61d20f1a5855296a0d8bdae82b44a16e202b266bbd244c7918298b5d27ae28c775ff8f9b03220a9ace2f4e10474ae6db402f4deca0c54553cfabc348f8a3c45cb960b1272fb9434d0168af93a8d0201671b224fdca05281873e7cb5b3bb96a52657ed4de823c65422b44e1302509a8267ca8dc868ae3014add43e25c862210bc54118ab89893aa53f8da4adc008081a5dd95b50db9322f93a90b3daa5d36ce09680e6aad0f2a1bfe79d0136bf99bdeb13572ea6ed03e85f5e124eae9aad92310129a195f1ee97045db33f0acae6f13e7d3c909b9746901116c91b6d594574ba33192d244b82bdd03dc9c8bd88b6515257a95f85314037a6dd12291b9982d05eb87c83defcc47344f0200c4d395ad6fd43dce7475f284bb82e71efbef5aa1d58ce5dad95c5d584997e24629a6879afe7d06b6191564c9ace2ad8db1f64d0a0a6d5df4e7d5bd2868ef48347662e3d8ab3e579962c2a161588fc8fdde43bdf32ff4132aceed418999a5db02d3a38bacbc18360a42bc4f5a0ee6f4ae6d21f824ec237f2e9ca10ada9d9966a6d87a07f9313f6701702f12dd5b8cd1cce1e7cdeeac49bdfa3be62f1ec03d41ab5b7c7914d7ad5723d0e7acd8d2cac1d6473114e6562d08b27b4575864d22ca749ff148dd3478952cb03b663655fcd40cc26c4b444bada92fbbc6bbae16fc56316252dca3bf230c6b24d693f6f285da')
key = data[:48][::-1]
data = data[48:]
b64_data = rc4crypt(data, key)
import base64
import struct
cmp_data = base64.b64decode(b64_data)
decompressed_data_len = struct.unpack('<I',cmp_data[:4])
print("decompressed data len: %d" % decompressed_data_len)
out = aplib_decompress(cmp_data[4:])
SHELLCODE_FILE = '/tmp/dridex_shell1.bin'
open(SHELLCODE_FILE, 'wb').write(out)
gap_size = 68
for i in range(0, gap_size, 4):
print(f"DWORD dword_{i:0x};")
shell_data_2 = unhex('4b93941a5f7671df49d159c96b178e1c96667e2ab79238f78d625bd468c1f2aee57a345f03721206fdee52034c3453b52c5d1fc76b2785b91e3442b9ea78da4e4f20eccb69cc1c0f2001fe7ccb6e9c5f8977f6fa89358a57ffd44f5c1e29213a3deaeb45160c655972cfcfc04a1260a89eff479e263dd500f6ec1ed713acb5bff9e2de331dc8dee30a67802dd8c66373af26c356d488013d52a220df8609d784ecad7ddeb888d436aa0c5451b69f2f03f51041538f3a2dcaf92db0968e27c81f9c34ee317777acaf53ecba288b5f0803cf29ba6170fe83f96d5c702a1c2aea4ebd21df44c62eb956db897631675f8e3f5f79e2b1498a47043b56d31d146c53fc53751433f89e8e2828c4bab1ddd49b13ec78fe91b890e617c907fde9653527b07d31801d42418e23fb9b2d715ed8b2a07495e102c97b36183e29e2baf47f')
key = shell_data_2[:48][::-1]
data = shell_data_2[48:]
b64_data = rc4crypt(data, key)
import base64
import struct
cmp_data = base64.b64decode(b64_data)
decompressed_data_len = struct.unpack('<I',cmp_data[:4])
print("decompressed data len: %d" % decompressed_data_len)
out = aplib_decompress(cmp_data[4:])
SHELLCODE_FILE_2 = '/tmp/dridex_shell2.bin'
open(SHELLCODE_FILE_2, 'wb').write(out)
data = unhex('63208a6488442f57a8414b4928cc1bb9660867a6c23f352adfa702ff121ceee14c69499849e264128bb21bda7fa2a8493244d4377003ec3caae05dc6a76ac7678acd82ca4b2ccceaf0602504576bc9c96223fda05aba4bbff6769f74bc7a3645839a41c4f610217d74d7d203cf38a89af0c05566f5e9301ea7d83f5102e8f324f90d3d38c0773e1a8db5dc6fb5808076269b686b40c6380c37de737d8838e530fac67a')
key = data[:48][::-1]
data = data[48:]
out = rc4crypt(data, key)
for s in out.split(b'\x00\x00'):
print(s.replace(b'\x00',b'').decode('latin-1'))
data = unhex('95c985f019ba926e4c776a4f39bb87c3af575e04263a3cfbf446fe76cd45826bdfc9c381775b910daa5af895189ac2037f94d7e0cbcb9f04a44b3dade44673637e63d0aeb931ee404dee9737afe9df5fefac345edde26397a125a483bbf9e4a80012c314d14eab1fe2a997e969b313ef290f7700bd7f3a3afb3f8a493f1449ff72a2e5163c632c7a79d450e1f8618cfaa15cba132da34e83c5dfe1aecf4b016b10e489041d096388fb2ada047bc1567732fe4b2c7d7b249be25e74ee1a6ee47abda1b8efd31253186df54c518aa596c5351fbe1cd148ef4bab2645')
key = data[:48][::-1]
data = data[48:]
out = rc4crypt(data, key)
for s in out.split(b'\x00\x00'):
print(s.replace(b'\x00',b'').decode('latin-1'))
data = unhex('763204228337e0fd87cd26f15d317e7753125863474a2a23cb087289562e629b87de7aebca904d887233a895007c1b7c8b01fc71443ef6ffb96a7a857d8b636bd5a0d3')
key = data[:48][::-1]
data = data[48:]
out = rc4crypt(data, key)
for s in out.split(b'\x00\x00'):
print(s.replace(b'\x00',b'').decode('latin-1'))
data = unhex('9929d0667cd2bee6ce087b584cfa8d54a813828fe28e53422b76d901b32b276fd6bf3583d4c0078646c3f7e0d908c93f798c881c00a9f7b23498892e6dea7634eaa2b1c7e6c67b34f153c9de05b775602a824dc48d52a7175c86e0112db594a53b7fd4c79689ce1adbd8b8fada3a0c03b8b980fe72f8af41ed8ed3af9a544cdf15c7e3ed8e878f61eaa41b626b5d450e65fd78b8736da4aeb1504d65352e081c877863842ab6adae783a99d525cdeb0eb078f8ee748283ce9554b88e9053b63f4c79e4479f117a341f97d7de18f4276b73ec5db445db5865bb16c8f2d819103d436c1b208cf00ee656e799559082d0996e8d89a07202915cb38ea1f3ad813b08034385703110dba092baf240fb0b8d839a07befcb05830c4a8ee25206c9539c010732a75670c8cba4db4eb8996d7d6f3012971574c3da3fb4a0e5274256ccbe3ee349e1fe5ab7249c78505cee79aa943beca99bdfb52f9aaae4d29ca9b3f26db612a1d866a2162ab96a07ad4a69544b1573c2d6e2c7d0424c97aaa23fbff7b703d4ff88d24dda7')
key = data[:48][::-1]
data = data[48:]
out = rc4crypt(data, key)
for s in out.split(b'\x00\x00'):
print(s.replace(b'\x00',b'').decode('latin-1'))
data = unhex('0921161a62f46e068fcb7050fe9d5fb793d2666a91610f5c75813f2e6a7f6846478262fcb0bd4e0e5ecdb8763266b6dba380102939f258b4e4c6e3c31481976ae308b958793d404c3dfa35a79ac4551495846111e06c4f0e5c4f9dbcf4d26fd2c9d1130852a1bd812f97d6b9b92fb919866f0a1ab95167a0d111c8b214ed78f7a0aaa18dae2ee00fb34974eb3bbc3b778c3f88f4b29dc7d1efd1793d79fa4381e2d991e0115ffc398989f531053028cddcdec0151fa888d249030604224cae2dc1fc6fcb792c4a9943e12ed6c825a69342d275fe8f3652f8f3a79d355f461a67cd3b8b4700236b6d63683e638c5c83c80d0bac05eb7f40298f97e43f4f32af9d125643ebe245a5f65a181d70bcb147f63bcf24552e782565a7dd818a4439471ff4092097aeee12453c802332346c07e80a9b31d631428f299d11b6cdd962654eb918ef68f8e6b786f3c7fc4b473e74cc2721d4a292c1f8ab99e0b5968eb6d3452f1b9a1d12a6a2255281e44f56b7b0b87b30001b19958e6681d5272c560396580c87370eadf68ae1c1022fcb05c781bcf09635b2bd648aa6fa100320c79c6a8f86a7a2bc679dad4a31faa6358b061ca98259ad6497afb43a2a98a5d1a1061c8ada7c4710c32fb7742d68bfb6e0d52f3f56cc9519e75df72c377728c2bfc58aa75bad3b136c2ba610d23c02febad47f1394db071774fa9d058ebd512bc4a411466d2a6582a651a7443cc8a3ae106af4433099f5616d3414c09788359272ac4599c76b8686eeb26ad090de576a3345cf30ad95b8053233ecff47c4315e7d30cd41387e44b241d992625dfd4cf7b0f506797a89e85914d7593cc59e2e089fbbac5a56b96824af9773822e35b948616ff348d1545933b3eb8253c59d29ebfd4001d087a457ff4b49a6eb2b4a9a2d13b1dd3c4d29db90c3ddf4434fa81246f850b719cea0450c30438cf7ca272681a0a6a76ecb6fe7b460f6703f0e3ed0ffe9c43db993e3c07a97405ea42caba91c47070dd9080e32fcd678721a032956bb393c24714c88')
key = data[:48][::-1]
data = data[48:]
b64_data = rc4crypt(data, key)
print(b64_data)
out = base64.b64decode(b64_data)
print(out)
SHELLCODE_FILE = '/tmp/dridex_shell_2.bin'
open(SHELLCODE_FILE, 'wb').write(out)
print("%d.%d.%d.%d" % (0x67, 0x2a, 0x38, 0x0f))
import re
import pefile
import struct
# Change to be your own local file path
SAMPLE_FILE_PATH = '/tmp/dridex.bin'
file_data = open(SAMPLE_FILE_PATH, 'rb').read()
ip_parse_egg = rb'\xbb(....)\x89\x45\x00\x0f\xb7\x53\x04\x89\x10\x0f\xb6\x4b\x0b\x83\xf9\x0a'
pe = pefile.PE(data=file_data)
data_section = None
for s in pe.sections:
if s.Name == b'.data\x00\x00\x00':
data_section = s.get_data()
match = re.search(ip_parse_egg, file_data, re.DOTALL|re.MULTILINE)
ip_table_addr = None
if match:
table_addr = struct.unpack('<I',match.group(1))[0]
print("Found data table at: %s\n" % hex(table_addr))
# Turn address into rva/offset and calculate table offsets
table_offset = pe.get_offset_from_rva(table_addr - pe.OPTIONAL_HEADER.ImageBase)
ip_table_offset = table_offset + 0xb
version_table_offset = table_offset + 4
# Get bot version
bot_version = struct.unpack('<H', file_data[version_table_offset:version_table_offset+2])[0]
print("Bot Version: %d\n" % bot_version)
ip_table_len = ord(file_data[ip_table_offset:ip_table_offset+1])
print("IP table length: %d" % ip_table_len)
# Move to actual IP table start
ip_table_offset += 1
# Extract the c2 ips
c2_ips = []
for i in range(ip_table_len):
ip_string = "%d.%d.%d.%d" % (ord(file_data[ip_table_offset:ip_table_offset+1]),
ord(file_data[ip_table_offset+1:ip_table_offset+2]),
ord(file_data[ip_table_offset+2:ip_table_offset+3]),
ord(file_data[ip_table_offset+3:ip_table_offset+4]))
port_string = struct.unpack('<H', file_data[ip_table_offset+4:ip_table_offset+6])[0]
print("%s:%s" % (ip_string,port_string))
c2_ips.append("%s:%s" % (ip_string,port_string))
ip_table_offset += 6