Zloader(又叫 Terdot、DELoader 或 Zeus Sphinx)是 2016 年 5 月出现的恶意软件,在最近几周又出现了活跃的迹象。
简介
如下所述,Brad Duncan 分析指出其使用的 DGA 域名:
样本会生成以 .com 为顶级域名的随机二十个字母字符的字符串,以此为域名进行 DNS 查询,以下是一些域名示例:
jgqhigsjkulmsvvhshmk.com
wapjdxlstholqwakofgi.com
aiavxvlshmkweccksfky.com
liswrfujohqsnbnohetn.com
hciqylualwcnyvajdkqq.com
pdtlshacpbacpnhcndpd.com
kdacggcctwcavdgvpbmk.com
wapwtpwciertrhkdaxrp.com
shyjgiyhyegxeqqpdtya.com
gccggcctwcerlshacpba.com
cpnhcndpdkylibtlbeco.com
bxhwpdkqdakbplfvfqwn.com
bioonshmwrbecckfcavh.com
TomasP 通过逆向分析 DGA 算法确认了这一发现,但并未发布细节。Dynamic Analysis 随后在 Pastebin 上发布了其 DGA 域名,但只针对一个特定的种子。
这篇文章对算法进行了逆向分析,并且使用 Python 对该算法进行了实现。分析基于下述样本:
属性 | 值 |
---|---|
MD5 | afdf2fbc0756ed304d1a33083a5f2b0f |
SHA1 | f3a25627f925390097a64a84ef34c952fe8af036 |
SHA256 | a947c216ea52ce23457b3babb1e1eb6275cabe2150d3995553e4de4b8c3d97f4 |
大小 | 323 KB(330752 字节) |
编译时间戳 | 2019-05-27 07:19:22 UTC |
链接 | MalwareBazaar,URLHaus,Twitter, VirusTotal |
文件名称 | antiamsi.bin (MalwareBazaar),antiamsi.bin (VirusTotal) |
检测结果 | MalwareBazaar: ZLoader, Virustotal: 52/74 as of 2020-04-25 03:46:18 - TrojanSpy:Win32/Glupteba.ef0afc48 (Alibaba), Trojan:Win32/Glupteba.RRS!MTB (Microsoft), Win32.Trojan-spy.Zbot.Lscl (Tencent), Trojan-Spy.Win32.Zbot.zzac (ZoneAlarm) |
一如既往,样本是加壳的,脱壳后可以得到以下样本:
属性 | 值 |
---|---|
MD5 | c844efe1b7e76cbdea36ce62ff788de9 |
SHA1 | d8143cf09bff7b0ca2a0c777912746a5922104ee |
SHA256 | 835048e00ba3babf6f920c9a4c2863865a5dcf8e0b6ede4f57c63aeb9cb5c147 |
大小 | 184 KB(188416 字节) |
编译时间戳 | 2020-04-08 18:19:58 UTC |
链接 | MalwareBazaar,Malpedia,Dropped_by_md5, VirusTotal |
检测结果 | Virustotal: 30/74 as of 2020-04-25 20:55:07 - a variant of Win32/Spy.Zbot.ADI (ESET-NOD32), W32/Zbot.ADI!tr (Fortinet), HEUR:Backdoor.Win32.Dridex.vho (Kaspersky), BehavesLike.Win32.Adopshel.ch (McAfee-GW-Edition), HEUR:Backdoor.Win32.Dridex.vho (ZoneAlarm) |
样本在挂起状态下创建了一个新的 Windows 安装程序进程 msiexec.exe,然后将自身的加密副本与解密代码写入 msiexec.exe。将线程内容设置为解密代码并且恢复线程执行。解密代码会解密注入的文件并跳转到偏移量为 0x1C90 的第一个 subroutine。将入口点设置为起始点转存了样本,如果将 0x03090000 作为加载镜像基址应该可运行样本。
属性 | 值 |
---|---|
MD5 | 5c76c41f9d0cc939240b3101541b5475 |
SHA1 | da361ec6976d3d9225ce40951b26d1d8ecdb7fd1 |
SHA256 | 4029f9fcba1c53d86f2c59f07d5657930bd5ee64cca4c5929cbd3142484e815a |
大小 | 208 KB(212992 字节) |
编译时间戳 | 2020-04-08 18:19:58 UTC |
链接 | MalwareBazaar,Malpedia,Dropped_by_md5, VirusTotal |
检测结果 | Virustotal: 22/74 as of 2020-04-25 20:55:24 - Win32/Spy.Zbot.ADI (ESET-NOD32), BScope.Trojan-Spy.Zbot (VBA32) |
以下分析基于最后一个样本(f3f2393a838d417ff8f823a235bd83f2)加载到镜像基址 0x03091CD2 上展开。
逆向工程
样本分析从三方面展开:
- 针对已加密的字符串,使用 IDA Pro 的 Appcall 函数进行动态解密
- 使用函数哈希进行动态解析来隐藏 API 调用。Appcall 可以评估程序得到绝大多数 API 名称
- Constant Unfolding、Dead Code 插入、通过特征进行算术替换。前两个大多被 Hex Rays 反编译器移除了,算术特征也可以很容易地被基本逻辑等价简化。在分析 DGA 时会举一个例子进行解释说明
字符串解密需要一个函数参数-密文的偏移量:
.text:03091CDB 68 B4 CA 0B 03 push offset dword_30BCAB4
.text:03091CE0 E8 1B 17 01 00 call decrypt_string ; BOT-INFO
通过运行以下 IDA 脚本即可找到带有纯文本的 decrypt_string 函数调用旁的注释:
from idc import *
from idautils import *
import idaapi
import sys
import string
import re
RESOLVER_TYPE_DEC = "char *__cdecl decrypt_string(char *a1, char *a2);"
m = re.search("\s([^ (@]+)[(@]", RESOLVER_TYPE_DEC)
RESOLVER_NAME = m.group(1)
resolver_addr = get_name_ea_simple(RESOLVER_NAME)
if resolver_addr == idaapi.BADADDR:
print(RESOLVER_NAME + " not defined")
sys.exit()
resolver = idaapi.Appcall.typedobj(RESOLVER_TYPE_DEC)
resolver.ea = resolver_addr
def previous_heads(ea):
""" iterator to get previous instructions of an address (no including itself) """
if not idc.is_head(idc.get_full_flags(ea)):
ea = idaapi.next_head(ea, ea+1000)
ea = idaapi.prev_head(ea,0)
while ea != idaapi.BADADDR:
yield ea
ea = idaapi.prev_head(ea, 0)
def do():
""" count the nr of references to the resolver function """
xrefs = list(CodeRefsTo(resolver_addr,1))
""" iterate over all references """
for i, xr in enumerate(xrefs):
print("[-] tackling {:08X}".format(xr))
args = []
for x in previous_heads(xr):
args.append(get_operand_value(x, 0))
if len(args) >= 1:
break
empty = Appcall.buffer(" ", 1000)
args.append(empty)
try:
r = resolver(*args)
except Exception as e:
print("FAILED: appcall failed: {}".format(e))
continue
try:
name = empty.value
except:
print("FAILED: to read back buffer)
continue
print("OK: found {}".format(name))
set_cmt(xr, name, True)
do()
Windows API 函数,如 InternetConnectA
会被动态解析后调用:
.text:030917DC 68 E1 75 E7 0A push 0AE775E1h
.text:030917E1 6A 13 push 13h
.text:030917E3 E8 88 19 01 00 call resolve_api ; wininet_InternetConnectA
.text:030917E8 83 ** 08 add esp, 8
.text:030917EB 0F B7 4D 10 movzx ecx, [ebp+arg_8]
.text:030917EF 6A 00 push 0
.text:030917F1 6A 00 push 0
.text:030917F3 6A 03 push 3
.text:030917F5 6A 00 push 0
.text:030917F7 6A 00 push 0
.text:030917F9 51 push ecx
.text:030917FA 53 push ebx
.text:030917FB 56 push esi
.text:030917FC FF D0 call eax
使用与解密字符串相似的 IDA Pro 脚本来查找 API 名称并注释反汇编代码。
列出所有对字符串解密代码的应用,可以看到其中产生了明文 .com
:
由于在使用字符串前对字符串进行解密,因此对 .com
的解密与 DGA 代码紧密相关。IDA Pro 对这段程序的反编译做的很好,只需要重命名一些变量就可以得到如下 C 代码:
int __cdecl the_dga(int dwSeed, int nNumberOfDomains, int pArrayOfDomains)
{
int result; // eax
unsigned int r; // esi
int i; // edi
unsigned int offset; // ebx
char the_letter; // al
unsigned int dwSeedXored_1; // ebx
char *szTLD_1; // eax
int i_1; // [esp-10h] [ebp-48h]
char szTLD[19]; // [esp+1h] [ebp-37h]
_DWORD the_domain_object[3]; // [esp+14h] [ebp-24h]
unsigned int dwSeedXored; // [esp+20h] [ebp-18h]
int iDomainNr; // [esp+24h] [ebp-14h]
char szDomain[13]; // [esp+2Bh] [ebp-Dh]
if ( nNumberOfDomains )
{
r = dwSeed;
result = 0;
dwSeedXored = dwSeed ^ 0x81716ECC;
do
{
iDomainNr = result;
initialize(the_domain_object);
i = 0;
do
{
offset = r % get_nr_25();
the_letter = offset + get_nr_97();
dwSeedXored_1 = dwSeedXored;
szDomain[0] = the_letter;
update_domain_object(szDomain);
r = dwSeedXored_1 ^ or(~(r + szDomain[0]) & 0x81716ECC, (r + szDomain[0]) & 0x7E8E9133, 0);
i_1 = i++;
plus(i_1, 1, 0, 0);
}
while ( i != get_nr_20() );
szTLD_1 = decrypt_string(szTLDCiphertext, szTLD);
concatenate(szTLD_1);
save_in_array((_DWORD *)pArrayOfDomains, (int)the_domain_object);
reset(the_domain_object);
result = plus_0(iDomainNr + 0x6A6E645D, 1u, 0) - 0x6A6E645D;
}
while ( result != nNumberOfDomains );
}
return result;
}
此时代码可读性已经非常高了,唯一不明显的部分是随机数计算(变量r),该变量需要一些基本的逻辑计算。种子 dwSeed
为 s
,szDomain[0]
为 /
,则下一个数使用如下方法决定:
r=(s⊕0x81716ECC)⊕(∼(r+l)⋅0x81716ECC)+((r+l)⋅0x7E8E9133)
⋅
、⊕
和 +
代表了逻辑与、异或和或。两个常数具有以下关系:
0x81716ECC =∼ 0x7E8E9133 mod 2 32(此处为2的32次方)
进一步:
a⊕b=(∼a⋅b)+(a⋅∼b)
将 k 设置为 0x81716ECC
可以得到:
r=(s⊕k)⊕(∼(r+l)⋅k)+((r+l)⋅∼k)
=s⊕k⊕((r+l)⊕k)
=s⊕(r+l)
使用 Python 实现该 DGA 算法如下所示:
def dga(seed, nr_of_domains):
domains = []
r = seed;
for i in range(nr_of_domains):
domain = ""
for j in range(20):
letter = ord('a') + (r % 25)
domain += chr(letter)
r = seed ^ ( (r + letter) & 0xFFFFFFFF)
domain += ".com"
print(domain)
查看域名生成代码的调用方,可以看到如何进行种子计算:
.text:03095540 push ebp
.text:03095541 mov ebp, esp
.text:03095543 push ebx
.text:03095544 push edi
.text:03095545 push esi
.text:03095546 sub esp, 16Ch
.text:0309554C lea edi, [ebp+pS]
.text:03095552 mov [ebp+var_1C], ecx
.text:03095555 push edi
.text:03095556 call decrypt_config_rc4
.text:0309555B add esp, 4
.text:0309555E lea esi, [ebp+pArrayOfDomains]
.text:03095561 mov ecx, esi
.text:03095563 call sub_30BA8E0
.text:03095568 call get_today_at_0UTC
.text:0309556D mov [ebp+dwSeed], eax
.text:03095570 call sub_30A5260
.text:03095575 lea ecx, [ebp+dwSeed]
.text:03095578 push edi
.text:03095579 push eax
.text:0309557A push ecx
.text:0309557B call rc4_encrypt
.text:03095580 add esp, 0Ch
.text:03095583 mov edi, [ebp+dwSeed]
.text:03095586 call get_nr_of_domains
.text:0309558B push esi
.text:0309558C push eax
.text:0309558D push edi
.text:0309558E call the_dga
首先,使用 R 对 Zlader 的配置进行解密。R 密钥 djluflczrgefphtiwegc
被硬编码在样本中。该配置包含在使用 DGA 域名之前使用的硬编码域名(如果存在的话)。在配置的最后,新的 R** 密钥 q23Cud3xsNf3
用于为 DGA 算法设置种子:
s
TelegramCrypt
AntiAMSIdoc
http://wmwifbajxxbcxmucxmlc.com/post.php
http://pwkqhdgytsshkoibaake.com/post.php
http://snnmnkxdhflwgthqismb.com/post.php
http://iawfqecrwohcxnhwtofa.com/post.php
http://nlbmfsyplohyaicmxhum.com/post.php
http://fvqlkgedqjiqgapudkgq.com/post.php
http://cmmxhurildiigqghlryq.com/post.php
http://nmqsmbiabjdnuushksas.com/post.php
http://fyratyubvflktyyjiqgq.com/post.php
q23Cud3xsNf3
种子基于当前时间 00:00 UTC 的 UNIX 时间戳。该 32 位值以小端序表示,并且使用来自配置中的密钥(如 q23Cud3xsNf3
)进行 R** 加密这四个字节。然后将结果解释为种子的小端序表示,下列代码揭示了种子的生成过程:
key = "q23Cud3xsNf3"
rc4 = R**(key)
d = d.replace(hour=0, minute=0, second=0)
timestamp = int((d - datetime(1970, 1, 1)).total_seconds())
p = struct.pack("<I", timestamp)
c = rc4.encrypt(p)
seed = struct.unpack("<I", c)[0]
种子生成后进入 DGA 算法,现在就可以完全用 Python 重新实现该 DGA 算法。
算法重新实现
以下代码可用于为任意日期和 R** 种子值生成 Zlader 域名。例如,要使用种子 q23Cud3xsNf3
生成 2020 年 4 月 25 日的域名即可使用 dga.py -d 2020-04-25 --rc4 q23Cud3xsNf3
。相关程序可以在我的 GitHub 中找到。
from datetime import datetime
import struct
import argparse
class R**:
def __init__(self, key_s):
key = [ord(k) for k in key_s]
S = 256*[0]
for i in range(256):
S[i] = i
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
self.S = S
self.i = 0
self.j = 0
def prng(self):
self.i = (self.i + 1) % 256
self.j = (self.j + self.S[self.i]) % 256
self.S[self.i], self.S[self.j] = self.S[self.j], self.S[self.i]
K = self.S[(self.S[self.i] + self.S[self.j]) % 256]
return K
def encrypt(self, data):
res = bytearray()
for d in data:
c = d ^ self.prng()
res.append(c)
return res
def __str__(self):
r = ""
for i, s in enumerate(self.S):
r += f"{i}: {hex(s)}\n"
return r
def seeding(d, key):
rc4 = R**(key)
d = d.replace(hour=0, minute=0, second=0)
timestamp = int((d - datetime(1970, 1, 1)).total_seconds())
p = struct.pack("<I", timestamp)
c = rc4.encrypt(p)
seed = struct.unpack("<I", c)[0]
return seed
def dga(seed, nr_of_domains):
r = seed
for i in range(nr_of_domains):
domain = ""
for j in range(20):
letter = ord('a') + (r % 25)
domain += chr(letter)
r = seed ^ ((r + letter) & 0xFFFFFFFF)
domain += ".com"
print(domain)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--date", help="date when domains are generated")
parser.add_argument("-r", "--rc4",
help="rc4 key from config",
choices=["q23Cud3xsNf3","41997b4a729e1a0175208305170752dd", "kZieCw23gffpe43Sd"],
default="q23Cud3xsNf3")
args = parser.parse_args()
if args.date:
d = datetime.strptime(args.date, "%Y-%m-%d")
else:
d = datetime.now()
seed = seeding(d, args.rc4)
dga(seed, 32)
其他样本
作为参考,再列出另外三个分析过的样本,这些样本又使用了两外两个种子值。可以在 GitHub 上找到预先计算好的所有三个种子生成的 DGA 域名。
md5 | 种子 | 域名 |
---|---|---|
afdf2fbc0756ed304d1a33083a5f2b0f | q23Cud3xsNf3 | 域名列表 |
2169e871d4ca668d1872722d1a0695dc | q23Cud3xsNf3 | 域名列表 |
fa9b3dfdb4b97dfe0db5991472f89399 | 41997b4a729e1a0175208305170752dd | 域名列表 |
306212efebc6ac92000687393e56a5cb | kZieCw23gffpe43Sd | 域名列表 |
2169e871d4ca668d1872722d1a0695dc
属性 | 值 |
---|---|
MD5 | 2169e871d4ca668d1872722d1a0695dc |
SHA1 | add2bbbac042c328ed71c9fd2efcb9cbce5a89f7 |
SHA256 | cc87e6581ca91f941f65332b2de0e681d58491b54aff9d0b30afae828a5f5790 |
大小 | 539 KB(552448 字节) |
编译时间戳 | 2020-04-14 11:20:46 UTC |
链接 | MalwareBazaar,URLhaus,VirusTotal |
文件名称 | SecuriteInfo.com.Win32.GenKryptik.EILT.4491(MalwareBazaar),output.155861665.txt,Southput,Southput.DLL,znvmzdd.dll,ZnVmZdD.dll,april14.dll(VirusTotal) |
检测结果 | Virustotal: 42/75 as of 2020-04-18 16:11:27 |
脱壳后:
属性 | 值 |
---|---|
MD5 | 6a900d6f8af3a1a0e31ca5bb63637d03 |
SHA1 | 221ab3d8ab16a0a7790026aab9b26904be6db436 |
SHA256 | e4d0a79d2463c5d3a71874e3389fa753f480b96639ad32baf1997baf8e5f714a |
大小 | 187 KB(191488 字节) |
编译时间戳 | 2020-04-08 18:20:42 UTC |
链接 | MalwareBazaar,Malpedia,Dropped_by_md5, VirusTotal |
检测结果 | Virustotal: 29/75 as of 2020-04-25 20:58:26 |
配置中使用 R** 密钥(edykepkrqahpyxabcwgm)进行加密,以下是硬编码域名:
http://wmwifbajxxbcxmucxmlc.com/post.php
http://ojnxjgfjlftfkkuxxiqd.com/post.php
http://pwkqhdgytsshkoibaake.com/post.php
http://snnmnkxdhflwgthqismb.com/post.php
http://iawfqecrwohcxnhwtofa.com/post.php
http://nlbmfsyplohyaicmxhum.com/post.php
http://fvqlkgedqjiqgapudkgq.com/post.php
http://cmmxhurildiigqghlryq.com/post.php
http://nmqsmbiabjdnuushksas.com/post.php
http://fyratyubvflktyyjiqgq.com/post.php
用于生成 DGA 种子的 R** 密钥为 q23Cud3xsNf3
。
fa9b3dfdb4b97dfe0db5991472f89399
属性 | 值 |
---|---|
MD5 | fa9b3dfdb4b97dfe0db5991472f89399 |
SHA1 | 5677f26e926c8c8d7f7bf7eb085a9e48549a268b |
SHA256 | 3648fe001994cb9c0a6b510213c268a6bd4761a3a99f3abb2738bf84f06d11cf |
大小 | 512 KB(524288 字节) |
编译时间戳 | 2020-04-20 10:48:16 UTC |
链接 | MalwareBazaar,URLHaus,Twitter, VirusTotal |
文件名称 | f.dll (MalwareBazaar), Letter ease, Letter ease.DLL, f.dll (VirusTotal) |
检测结果 | MalwareBazaar: ZLoader, Virustotal: 50/75 as of 2020-04-24 02:51:48 |
脱壳后:
属性 | 值 |
---|---|
MD5 | 133b1861b3590bf00308509227f82872 |
SHA1 | eb6f12759da7aa84077143e3e2694b6fda3d5631 |
SHA256 | dd11381223ab1902db2963df4cbe3299e42064a5857545560f913647c1f70c5a |
大小 | 187 KB(191488 字节) |
编译时间戳 | 2020-04-08 18:20:42 UTC |
链接 | MalwareBazaar, Malpedia, Dropped_by_md5, VirusTotal |
检测结果 | Virustotal: 29/74 as of 2020-04-25 21:00:11 |
配置中使用 R** 密钥(dqhfltvppmucpvebkqtn)进行加密,以下是硬编码域名:
https://dcaiqjgnbt.icu/wp-config.php
https://nmttxggtb.press/wp-config.php
用于生成 DGA 种子的 R** 密钥为 41997b4a729e1a0175208305170752dd
。
306212efebc6ac92000687393e56a5cb
属性 | 值 |
---|---|
MD5 | 306212efebc6ac92000687393e56a5cb |
SHA1 | dc0b678e9ad7cadd5de907bf80fa351d5d3347cc |
SHA256 | 8d5a770975e52ce1048534372207336f6cc657b43887daa49994e63e8d7f6ce1 |
大小 | 856 KB(877056 字节) |
编译时间戳 | 2020-04-05 16:19:02 UTC |
链接 | MalwareBazaar, VirusTotal |
文件名称 | JtVhjtbGMAbrWft.dll (MalwareBazaar), FfIYXQPKpCQymHQ.exe, PkRWAytIAsEHwhy.exe, qKMCMByhJjQpfmZ.exe, FmgJjYLZmscJaur.exe, gGwBVwnpxkyFNlc.exe, ZhbIdJYZzrkPQGs.exe, eIGmAdVpMFJxmrk.exe, VUCJyZshHrMGvdT.exe, WHFQhvaOzqkkTFk.exe, dFVlQGPNqrdhrCE.exe, tnXoUCMnjELKOYm.exe, dTEAUJnMdnADEVG.exe, omih.dll, ikhaapd.dll, 2020-04-07-ZLoader-DLL-binary.bin, etidwuv.dll, ekydn.dll, upiqwoq.dll, ryubn.dll, JtVhjtbGMAbrWft.exe, icobyg.dll, GnbjtDwFOsvocUW.exe, CbxfejTbfqXuuIT.exe, JtVhjtbGMAbrWft.bin (VirusTotal) |
检测结果 | Virustotal: 58/75 as of 2020-04-20 00:40:47 |
脱壳后:
属性 | 值 |
---|---|
MD5 | 4a74e2d34230bbc705f39e6943c859d3 |
SHA1 | 410c1c03a52dbd56e78b0487ec532e68eb1c64e4 |
SHA256 | 60544c6694620488b69e568b15c96b33971dd7343ba63da31f993332852871c2 |
大小 | 172 KB(176640 字节) |
编译时间戳 | 2020-03-30 18:35:43 UTC |
链接 | MalwareBazaar, Malpedia, Dropped_by_md5, VirusTotal |
检测结果 | Virustotal: 34/75 as of 2020-04-25 20:59:59 |
配置中使用 R** 密钥(cbstobypqnbsnnpehdtb)进行加密,以下是硬编码域名:
https://knalc.com/sound.php
https://namilh.com/sound.php
https://ronswank.com/sound.php
https://stagolk.com/sound.php
https://mioniough.com/sound.php
https://ergensu.com/sound.php
用于生成 DGA 种子的 R** 密钥为 kZieCw23gffpe43Sd
。
参考来源
*FB 小编 Avenger 编译,转载请注明来自 FreeBuf.COM