freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Zloader的DGA算法解析
2020-06-23 13:00:13

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 对该算法进行了实现。分析基于下述样本:

属性
MD5afdf2fbc0756ed304d1a33083a5f2b0f
SHA1f3a25627f925390097a64a84ef34c952fe8af036
SHA256a947c216ea52ce23457b3babb1e1eb6275cabe2150d3995553e4de4b8c3d97f4
大小323 KB(330752 字节)
编译时间戳2019-05-27 07:19:22 UTC
链接MalwareBazaarURLHausTwitterVirusTotal
文件名称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)

一如既往,样本是加壳的,脱壳后可以得到以下样本:

属性
MD5c844efe1b7e76cbdea36ce62ff788de9
SHA1d8143cf09bff7b0ca2a0c777912746a5922104ee
SHA256835048e00ba3babf6f920c9a4c2863865a5dcf8e0b6ede4f57c63aeb9cb5c147
大小184 KB(188416 字节)
编译时间戳2020-04-08 18:19:58 UTC
链接MalwareBazaarMalpediaDropped_by_md5VirusTotal
检测结果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 作为加载镜像基址应该可运行样本。

属性
MD55c76c41f9d0cc939240b3101541b5475
SHA1da361ec6976d3d9225ce40951b26d1d8ecdb7fd1
SHA2564029f9fcba1c53d86f2c59f07d5657930bd5ee64cca4c5929cbd3142484e815a
大小208 KB(212992 字节)
编译时间戳2020-04-08 18:19:58 UTC
链接MalwareBazaarMalpediaDropped_by_md5VirusTotal
检测结果Virustotal: 22/74 as of 2020-04-25 20:55:24 - Win32/Spy.Zbot.ADI (ESET-NOD32), BScope.Trojan-Spy.Zbot (VBA32)

以下分析基于最后一个样本(f3f2393a838d417ff8f823a235bd83f2)加载到镜像基址 0x03091CD2 上展开。

逆向工程

样本分析从三方面展开:

  1. 针对已加密的字符串,使用 IDA Pro 的 Appcall 函数进行动态解密
  2. 使用函数哈希进行动态解析来隐藏 API 调用。Appcall 可以评估程序得到绝大多数 API 名称
  3. 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

1.png

由于在使用字符串前对字符串进行解密,因此对 .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),该变量需要一些基本的逻辑计算。种子 dwSeedsszDomain[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种子域名
afdf2fbc0756ed304d1a33083a5f2b0fq23Cud3xsNf3域名列表
2169e871d4ca668d1872722d1a0695dcq23Cud3xsNf3域名列表
fa9b3dfdb4b97dfe0db5991472f8939941997b4a729e1a0175208305170752dd域名列表
306212efebc6ac92000687393e56a5cbkZieCw23gffpe43Sd域名列表

2169e871d4ca668d1872722d1a0695dc

属性
MD52169e871d4ca668d1872722d1a0695dc
SHA1add2bbbac042c328ed71c9fd2efcb9cbce5a89f7
SHA256cc87e6581ca91f941f65332b2de0e681d58491b54aff9d0b30afae828a5f5790
大小539 KB(552448 字节)
编译时间戳2020-04-14 11:20:46 UTC
链接MalwareBazaarURLhausVirusTotal
文件名称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

脱壳后:

属性
MD56a900d6f8af3a1a0e31ca5bb63637d03
SHA1221ab3d8ab16a0a7790026aab9b26904be6db436
SHA256e4d0a79d2463c5d3a71874e3389fa753f480b96639ad32baf1997baf8e5f714a
大小187 KB(191488 字节)
编译时间戳2020-04-08 18:20:42 UTC
链接MalwareBazaarMalpediaDropped_by_md5VirusTotal
检测结果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

属性
MD5fa9b3dfdb4b97dfe0db5991472f89399
SHA15677f26e926c8c8d7f7bf7eb085a9e48549a268b
SHA2563648fe001994cb9c0a6b510213c268a6bd4761a3a99f3abb2738bf84f06d11cf
大小512 KB(524288 字节)
编译时间戳2020-04-20 10:48:16 UTC
链接MalwareBazaarURLHausTwitterVirusTotal
文件名称f.dll (MalwareBazaar), Letter ease, Letter ease.DLL, f.dll (VirusTotal)
检测结果MalwareBazaar: ZLoader, Virustotal: 50/75 as of 2020-04-24 02:51:48

脱壳后:

属性
MD5133b1861b3590bf00308509227f82872
SHA1eb6f12759da7aa84077143e3e2694b6fda3d5631
SHA256dd11381223ab1902db2963df4cbe3299e42064a5857545560f913647c1f70c5a
大小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

属性
MD5306212efebc6ac92000687393e56a5cb
SHA1dc0b678e9ad7cadd5de907bf80fa351d5d3347cc
SHA2568d5a770975e52ce1048534372207336f6cc657b43887daa49994e63e8d7f6ce1
大小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

脱壳后:

属性
MD54a74e2d34230bbc705f39e6943c859d3
SHA1410c1c03a52dbd56e78b0487ec532e68eb1c64e4
SHA25660544c6694620488b69e568b15c96b33971dd7343ba63da31f993332852871c2
大小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

参考来源

Johannesbader

*FB 小编 Avenger 编译,转载请注明来自 FreeBuf.COM

# 恶意软件 # zloader # 算法解析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者