飘云阁安全论坛

 找回密码
 快速注册

QQ登录

只需一步,快速开始

查看: 306|回复: 0

[病毒分析] [翻译]Melting Ice-用几个简单的步骤跟踪IcedID服务器

  [复制链接]
  • TA的每日心情
    开心
    2019-3-15 11:00
  • 签到天数: 262 天

    [LV.8]以坛为家I

    发表于 2021-6-1 11:36:21 | 显示全部楼层 |阅读模式
    本帖最后由 梦幻的彼岸 于 2021-6-1 18:01 编辑

    备注

    简介
    追踪僵尸网络通常需要大量的努力、时间和威胁情报知识。如果是多阶段的复杂恶意软件家族,如IcedID、Emotet和QakBot,跟踪的难度就更大了。因此,作为恶意软件分析师,我们倾向于寻找尽可能多的自动化过程--收集大规模的样本,识别它们,提取它们的配置,然后让这些产生我们感兴趣的价值,如更清晰的威胁情报图片或更最新的域信誉引擎。
    虽然恶意软件分析的生活往往像上面描述的那样困难,但它不一定是这样。有时,如果我们很聪明,10%的工作会得到我们感兴趣的90%的结果。在这篇文章中,我们展示了这样一个巧妙的方法,并展示了如何快速跟踪IcedID C&C服务器,并且不需要跟踪或分析任何样本。
    IcedID背景
    IcedID banker恶意软件最早出现于2017年9月,此后取得了重大进展。这种威胁,也被称为BokBot,在过去的一年中不断增长,并拥有广泛的恶意功能,如浏览器拦截,凭证盗窃,MiTM代理设置和VNC模块等。
    MalwarebytesGroup-IBBinary Defense过去的出版物对该僵尸程序的内部功能,包括感染链和恶意组件进行了彻底描述。
    根据二进制防御研究人员提出的最新感染链,最近迭代的IcedID包含三个恶意组件--包含恶意宏的进入点DOC/XLS;一个第一阶段的payload;以及最后一个第二阶段的payload,它本身由两个子部分组成--一个64位DLL加载器,以及被伪装成".dat "文件的加密的实际僵尸程序。
    每个第二阶段的payload通常包含2-4个独特的嵌入式域名,这些域名都可以解析到同一个IP地址。假设我们设定一个目标,跟踪这些域名/IP并阻止它们;自然,我们对最快速和无痛的方式感兴趣。
    受害者的通信协议是HTTPS,所以我们检查了捆绑的TLS证书。正如我们在下面观察到的,IcedID的人更关心RSA-2048提供的硬性具体保证,而不太关心遵循安全准则;该证书签发给默认名称为“ Internet Widgits Pty,Ltd”的机构,该机构位于“某个州”。
    IcedID C&C证书样本:
    1.png
    如果我们把这个IcedID证书和一个默认的自签名证书进行比较,最明显的区别是通用名称字段。公共服务器的FQDN(例如)应该在这个字段中出现,而不是像本例中的Checkpoint.comlocalhost那样。
    你可能明白这是怎么回事;我们立即开始扫描广泛的网络,寻找其他呈现这种证书的服务器。
    枚举服务器
    我们不愿重复发明,选择使用流行的互联网扫描平台Censys来创建一份潜在的C&C候选人名单。像Shodan这样的其他网络扫描平台也可以用于此目的。
    Censys引擎为我们提供了大量的证书查询控制。经过一些调整和调整,我们构造了以下查询:
    1. 443.https.tls.certificate.parsed.issuer_dn: "CN=localhost, C=AU, ST=Some-State, O=Internet Widgits Pty Ltd" and 443.https.tls.certificate.parsed.subject_dn: "CN=localhost, C=AU, ST=Some-State, O=Internet Widgits Pty Ltd"
    复制代码

    Censys的证书结果之一:
    2.png

    如果您怀疑在证书中使用通用名称的唯一性,可以通过一个简短的实验轻松地证明这一点:我们搜索到的几十个服务器看起来似乎很多,但如果您在搜索查询中省略了要求,结果将在千分之一的大小和数量上爆炸式增长:localhostCN=localhost

    目前,我们不能假设这些服务器中的每一个都是活动的IcedID恶意C&C,所以我们需要验证它们。幸运的是,这是可能的,这要归功于我们在研究过程中发现的另一种独特的特性。

    验证服务器
    当我们对僵尸程序的功能进行逆向工程时,我们注意到僵尸程序在与C&C服务器通信之前做了一个相当有趣的测试。下面的代码是一个回调函数的一部分,在与C&C.WinHttpSendRequest联系之前被调用。

    证书验证代码:
    3.png
    简单地说,这段代码在证书的公钥上运行Fowler-Noll-Vo 32位哈希函数,并将其与证书的指定序列号进行比较。只有当比较结果吻合时,通信才会继续进行(或用XOR-ed值)。0x384A2414

    证书序列号字段由证书颁发机构分配,并为每个生成的证书提供一个唯一的标识符。证书颁发者必须确保没有两个具有相同证书颁发者DN的不同证书包含相同的序列号,但没有人能够保证这一点。

    在我们的案例中,证书是自签的,恶意软件运营商根据上述逻辑唯一地分配其C&C序列号字段。自己使用这种验证算法,针对一个可疑的服务器,我们可以确保它是IcedID基础设施的一部分。我们在附录A中提供了一个简单的python脚本来验证这个逻辑,针对一个提供的远程服务器。

    应用该方法,我们发现之前的大多数潜在结果都与IcedID有关,并将名单缩小到52个服务器。考虑到独特的证书属性和不常见的哈希函数,我们可以安全地推断这些服务器是IcedID C&C基础设施中的组成部分。有了这个名单,我们去仔细查看了这些服务器。

    服务器分析

    通过分析他们面向互联网的标语,我们可以发现大多数部署的服务器有几个类似的属性。最常见的属性是开放端口、操作系统和网络服务器。

    开放的端口 - 所有的服务器都有443端口,94%的服务器还监听80端口,77%的服务器监听SSH端口(22)。

    操作系统 - 77%的服务器运行的是Debian操作系统。

    网络服务器 - 94%的服务器运行nginx网络服务器。

    我们还可以看到,大多数的服务器居住在罗马尼亚、美国和德国。

    IcedID服务器的位置:
    4.png
    另一个分析角度是通过被动DNS服务,如RiskIQ。使用这种服务使我们能够为我们拥有的每个IP地址找到相关的域,并扩大我们的威胁情报图片。这些额外的域在逻辑上必须嵌入一些我们无法直接访问的样本中。例如,我们发现的一个地址是 ,它未解析到以下IcedID域:

    152[.]89.247.60

    formgotobig[.]top
    ponduroviga[.]top
    tranmigrust[.]club
    aswenedo[.]space

    总结
    一个可悲的事实是,做 "正确的事情 "可能会适得其反。IcedID活动的维护者就是这种情况;他们制作了一个自签名的证书,并让他们的恶意操作支持HTTPS,这是一个值得称赞的努力,绝大多数恶意软件都不屑一顾,甚至一些长期运行的合法网站在很长一段时间内也不屑一顾。通过这样做,他们使煽动敌意接管他们的网络变得更具挑战性--但他们也使他们所有的服务器基本上以欢快的 "嗨,我是一个恶意的C&C "来回应标准扫描服务。

    一旦这些服务器被暴露,我们就能持续追踪它们,分析它们的行为,而不需要运行正则表达式,更不用说启动调试器或反汇编器。对于运行恶意活动的人来说,这是一个噩梦般的场景;他们的目的是将不断更新的C&C服务器列表中的 "奖品 "放在遥不可及的地方,放在层级列表的顶端。相反,由于这种太过聪明的TLS业务,收集服务器(并获得一堆多汁的信息,这不在本文的范围之内)变得在分析师的能力范围之内,只要有一周的经验。正如一位智者曾经说过的,在使用一种技术之前要先思考,否则你的对手会用它来对付你。

    IOC
    IcedID C&C servers:
    1. 83[.]97.20.249
    2. 83[.]97.20.174
    3. 194[.]5.249.52
    4. 45[.]153.240.135
    5. 194[.]5.250.104
    6. 152[.]89.247.60
    7. 91[.]193.19.97
    8. 194[.]5.249.81
    9. 83[.]97.20.73
    10. 194[.]5.250.35
    11. 83[.]97.20.176
    12. 194[.]5.250.46
    13. 212[.]114.52.186
    14. 91[.]193.19.37
    15. 45[.]147.231.113
    16. 188[.]119.148.75
    17. 185[.]38.185.90
    18. 45[.]138.172.179
    19. 91[.]193.19.51
    20. 83[.]97.20.122
    21. 194[.]5.249.103
    22. 139[.]60.161.63
    23. 194[.]5.249.86
    24. 139[.]60.161.48
    25. 185[.]33.85.35
    26. 194[.]5.249.97
    27. 79[.]141.164.241
    28. 194[.]5.249.90
    29. 193[.]109.69.52
    30. 194[.]5.249.54
    31. 185[.]70.184.87
    32. 79[.]141.166.39
    33. 146[.]0.77.92
    34. 31[.]184.199.11
    35. 45[.]129.99.241
    36. 194[.]5.249.46
    37. 185[.]123.53.202
    38. 146[.]0.77.18
    39. 31[.]24.228.170
    40. 45[.]147.230.88
    41. 83[.]97.20.254
    42. 45[.]147.230.82
    43. 194[.]5.249.72
    44. 46[.]17.98.191
    45. 45[.]153.241.115
    46. 185[.]70.184.41
    47. 139[.]60.161.50
    48. 194[.]5.249.143
    49. 79[.]141.161.176
    50. 5[.]149.252.179
    51. 45[.]147.228.198
    52. 91[.]193.19.251
    复制代码

    附录A
    测试一个服务器的IcedID证书:
    [Python] 纯文本查看 复制代码
    import idna
     
    from socket import socket
    from OpenSSL import SSL
    from cryptography.hazmat.primitives.serialization import Encoding
    from cryptography.hazmat.primitives.serialization import PublicFormat
     
    def fnv1a_32(data: bytes) -> int:
        """Fowler–Noll–Vo hash function variation.
     
        Args:
            data (bytes): Input data
     
        Returns:
            int: Output 32 bit hash
        """
        hval_init = 0x811c9dc5
        fnv_prime = 0x01000193
        fnv_size = 2 ** 32
     
        hval = hval_init
        for byte in data:
            hval = hval ^ byte
            hval = (hval * fnv_prime) % fnv_size
        return hval
     
    def get_certificate(hostname: str, port: int):
        """Connects to the remote server,
        and retrieves the certificate.
     
        Args:
            hostname (str): Remote hostname
            port (int): Remote port (usually 443)
     
        Returns: 
            Certificate object
        """
        hostname_idna = idna.encode(hostname)
     
        # We are building a SSL context on top of a raw socket.
        sock = socket()
        sock.connect((hostname, port))
        ctx = SSL.Context(SSL.SSLv23_METHOD)
        ctx.check_hostname = False
        # the cert is self-signed so we don't want to verify it
        ctx.verify_mode = SSL.VERIFY_NONE
     
        # making SSL handshake
        sock_ssl = SSL.Connection(ctx, sock)
        sock_ssl.set_connect_state()
        sock_ssl.set_tlsext_host_name(hostname_idna)
        sock_ssl.do_handshake()
     
        # retrieving certificate and converting it to an cryptography object
        cert = sock_ssl.get_peer_certificate()
        crypto_cert = cert.to_cryptography()
        sock_ssl.close()
        sock.close()
     
        return crypto_cert
     
    def test_is_icedid_c2(hostname: str, port: int) -> bool:
        """Testing whether a remote server is part of IcedID
        C&C infrastructure.
     
        Args:
            hostname (str): Remote hostname
            port (int): Remote port (usually 443)
     
        Returns:
            bool: True if the server is IcedID verified, or False otherwise.
        """
        try:
            # We query the server and retrieve its certificate.
            cert = get_certificate(hostname, port)
            serial_number = cert.serial_number
     
            # Getting the public key, and hashing it.
            public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.PKCS1)
            fnv_hash = fnv1a_32(public_key) & 0x7fffffff
     
            # Finally comparing the hash to the serial number.
            if serial_number == fnv_hash or serial_number == fnv_hash ^ 0x384A2414:
                return True
        except Exception as e:
            return False
        return False
    您需要登录后才可以回帖 登录 | 快速注册

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    快速回复 返回顶部 返回列表