【病毒分析】全网首发!袭扰国内top1家族Mallox家族破解思路及技术分享

时间: 2024-12-19 12:21:08 浏览量:46

  引言:根据360安全卫士官方论坛的最新统计数据,本月国内勒索软件受害者设备所中病毒家族中Mallox家族占比22.38%居首位。https://weishi.360.cn/dnzs/12640.html

1. 背景

1.1 前情提要

  Mallox家族的加密器是solar团队处理的众多案例中,分析次数最多的家族之一。本次分析的对象是2022年10月时间段的mallox后缀加密器。通过对样本的深入分析,我们捕获到了一些关键线索,这些线索为破解提供了重要思路。在下文中,我们将详细分享这些发现和分析过程,基于本篇思路制作的恢复工具会在下一篇工具分享中进行发布,敬请期待!

1.2 家族介绍

  关于Mallox家族信息可参考本篇文章【病毒分析】Mallox勒索家族新版本:加密算法全面解析,详情内容部分可移步solar官网处介绍https://www.solarsecurity.cn/family?id=3

1.3 支持的文件类型

  基于本篇文章提到的破解思路,针对Mallox家族的不同版本后缀,可以通过逆向分析获取相关密钥和算法进行恢复:

mallox版本后缀时间段HASH
.mallox2022年10月到2023年3月1f793f973fd906f9736aa483c613b82d5d2d7b0e270c5c903704f9665d9e1185
.xollam2023年1月10f96f64659415e46c3f2f823bdb855aab42d0bfced811c9a3b72aea5f22d880
.bitenc2023年1月a340ef5adb00a2bf1a0735600491ca98ac8045b57db892dedc27575a53b25056
.malox2023年4月到2023年7月0427a9f68d2385f7d5ba9e9c8e5c7f1b6e829868ef0a8bc89b2f6dae2f2020c4
.maloxx2023年6月03596723ecb5be777b1e3ec5deb414dc78155a30a466b2b25a4574f4db9a568a
.malloxx2023年8月4e4592a5dec8a0fc12bbd6f955cd01936aa80d7b8d54db5258140a8a1c460c66
.mallab2023年9月到2023年10月d1969e0ec464e8b09e54ea35fbe181ea391ed3674e0b35b0f6ee92821478e76c
.ma1x02024年2月003ea0712cd31a75f5dfb6a23d2d12ea1e615f7601574ade09b6b61e3b7ed2b0

2. 逆向分析

2.1 前置知识

  在分析如何破解之前,需要先提前了解一点mallox东西。

  mallox家族最为显眼的就是这部分,采用CryptGenRandom函数的方式获取随机数种子,那怕最近的一些变种版本,都是沿用此类函数来进行密钥生成。

  在获取到了4个字节的随机数之后,就会将该随机数当做是梅森旋转算法的随机数种子

  接着会初始化一个public_key,然后与后面的一组55个字节大小的内容(C盘序列号+CPUID+固定内容+计算机名称)的sha256进行一个cure25519算法的密钥生成。这里命名public_key和55字节大小的数据的sha256产生的share_keyuser_private_key

  这里命名public_key和55字节大小的数据的sha256产生的share_keyuser_private_key,接着再利用这个生产的私钥产生一个对应的公钥,然后再用这个私钥和黑客的hack_public_key生成一个共享密钥share_key。

  这里具体可以参考Babyk家族分析中的算法分析那部分的内容【病毒分析】BabyK加密器分析-Windows篇

  OK,到了目前为止,我们大致上了解了整体的流程如下:

  (注意:这里我并未将梅森算法和下面的流程连到一起,因为他们其实并不相干)

  接下来,我们去掉mallox章节的算法分析部分的内容,直接来看看,加密文件所使用的key到底是怎么生成的?

  (注意:这里如果看不懂,请看上一章节mallox算法分析部分的内容【病毒分析】Mallox勒索家族新版本:加密算法全面解析)

  首先,文件的加密函数在StarAddress函数中,并且梅森旋转算法中,生成随机数的部分也在这里,其中这个v9就是我们利用上面的梅森算法生成的256组每组4字节大小随机数,至于这个sub_E43C34、sub_E4142B和sub_E434函数,在这里就是对随机数进行变形的,所以就先不提,继续向下看。

  进入到enc_file函数,来看文件加密的具体过程。首先会先获取一下后续AES加密算法IV,获取方法就是从传入的256组随机数中,取后4组数据,然后一组数据是4个字节组成,一共16个字节。

  然后继续接着上面的v7的位置从后往前再取10组数据,一共40个字节,当做ChaCha20加密算法的KeyNonce

  接着就是开始初始化一下ChaCha20KeyNonce还有AESIV

  调用AES-128-CTR加密算法,来对40个字节的ChaCha20KeyNonce数据进行加密。

  到此,我们再做一次总结:

  但是这里有个问题,AES的KEY去哪里了???

  其实AES的KEY在AES_init函数中才会被用到,这里看一下具体内容

  在aes_init函数的初始化部分可以看到key的部分,主要取了上面密钥生成部分,最后share_keysha256的前16个字节的内容。

  好了,我们将此过程补充完整:

  最后,我们将要用Cha20Cha20的密钥去加密文件了。当然了,遵循传统勒索软件的加密规则“既要加密的牢,还要加密的快”,依然会采用根据文件大小来进行加密部分的计算:

  1 文件大小 < 0x2710 字节,文件不加密

  2 文件大小>=0x2710字节并且<= 0x19000字节,文件全部加密

  3 文件大小>0x19000字节采用条状加密。

  这里也补充一下条状加密的部分的规则,因为mallox算法分析那篇确实漏掉了。

  首先会将待加密的文件分成10个块,每块大小这里我称之为block_size。之后,循环十次,每次只加密每个块的前enc_size大小的内容,如图所示:

  了解了加密的结构以后,这里就将这个enc_size和block_size的计算公式放出来:

enc_size = 15 * (文件大小 / 10 / 100) & 0xfffff000

block_size = 文件大小 / 10

  但是这里还会对enc_size的大小做处理,假如,enc_size的大小,如果小于0x1000(4096)字节大小时,则会将其强行扩充到0x1000(4096大小)。

  以上就是全部的条状加密规则了。

  加密完毕文件以后,将会在文件末尾写入100个字节大小的数据,其中包括了如下内容:

  OK,到目前为止我们总结一下整个过程:

2.2 破解思路分析

  以上我们分析了从密钥生成到文件加密的全部过程,包括了每个数据的产生和由来。所以分析可以知道,破解的思路大致上只有两种:

  1 从随机数上下手,获取随机数种子

  2 从55个固定的字节下手

  这里我们先采用第二种方式,针对55个固定字符进行分析。

  这55字节部分内容主要包括:

  其中固定值部分是针对于每个mallox的变种后缀样本,定制生成的,所以会不一样。

  这里我们来模拟一下这个过程:

package main

/*
#cgo CFLAGS: -std=gnu11

#include <stdint.h>

// 使用GNU内联汇编调用CPUID
// 输入: op (EAX输入值)
// 输出: eax, ebx, ecx, edx
void cpuid(uint32_t op, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
    uint32_t a, b, c, d;
#if defined(__x86_64__) || defined(__i386__)
    __asm__ volatile (
        "cpuid"
        : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
        : "a"(op)
    );
#else
    // 非x86架构下这里可能需要不同实现或直接报错
    a = b = c = d = 0;
#endif
    *eax = a;
    *ebx = b;
    *ecx = c;
    *edx = d;
}
*/
import "C"
import (
    "encoding/binary"
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

func uint32ToBytes(num uint32, bigEndian bool) []byte {
    // 创建一个字节切片
    byteArray := make([]byte, 4)

    if bigEndian {
       // 使用大端字节序
       binary.BigEndian.PutUint32(byteArray, num)
    } else {
       // 使用小端字节序
       binary.LittleEndian.PutUint32(byteArray, num)
    }

    return byteArray
}

// GetVolumeSerialNumber 返回指定盘符的卷序列号
func GetVolumeSerialNumber(drive string) (uint32, error) {
    kernel32 := syscall.NewLazyDLL("kernel32.dll")
    pGetVolumeInformationW := kernel32.NewProc("GetVolumeInformationW")

    var (
       volumeNameBuffer     = make([]uint16, syscall.MAX_PATH+1)
       volumeSerialNumber   uint32
       maxComponentLength   uint32
       fileSystemFlags      uint32
       fileSystemNameBuffer = make([]uint16, syscall.MAX_PATH+1)
    )

    drivePtr := syscall.StringToUTF16Ptr(drive)

    ret, _, err := pGetVolumeInformationW.Call(
       uintptr(unsafe.Pointer(drivePtr)),
       uintptr(unsafe.Pointer(&volumeNameBuffer[0])),
       uintptr(len(volumeNameBuffer)),
       uintptr(unsafe.Pointer(&volumeSerialNumber)),
       uintptr(unsafe.Pointer(&maxComponentLength)),
       uintptr(unsafe.Pointer(&fileSystemFlags)),
       uintptr(unsafe.Pointer(&fileSystemNameBuffer[0])),
       uintptr(len(fileSystemNameBuffer)),
    )

    if ret == 0 {
       return 0, err
    }

    return volumeSerialNumber, nil
}
func get_disk_serial(disk_path string) ([]byte, error) {
    serial, err := GetVolumeSerialNumber(disk_path)
    if err != nil {
       fmt.Println("获取C盘序列号失败:", err)
       return nil, err
    }
    return uint32ToBytes(serial, false), err
}
func get_cpuid() ([]byte, []byte, []byte, []byte) {
    var eax, ebx, ecx, edx C.uint32_t
    C.cpuid(0, &eax, &ebx, &ecx, &edx)
    return uint32ToBytes(uint32(eax), false), uint32ToBytes(uint32(ebx), false), uint32ToBytes(uint32(ecx), false), uint32ToBytes(uint32(edx), false)
}
func get_hostname() ([]byte, error) {
    hostname, err := os.Hostname()
    if err != nil {
       fmt.Println("获取计算机名称失败:", err)
       return nil, err
    }
    return []byte(hostname), err
}
func get_whole_password() []byte {
    password, err := get_disk_serial("C:\\")
    if err != nil {
       return nil
    }
    eax, ebx, ecx, edx := get_cpuid()
    hostname, err := get_hostname()
    if err != nil {
       return nil
    }
    password = append(password, eax[:]...)
    password = append(password, ebx[:]...)
    password = append(password, ecx[:]...)
    password = append(password, edx[:]...)
    //这里到时候可以写个判断,来根据不同的加密版本的固定值进行选择拼接
    password = append(password, uint32ToBytes(uint32(0x0FADACE8), false)...)
    password = append(password, uint32ToBytes(uint32(0x9AB979DD), false)...)
    password = append(password, uint32ToBytes(uint32(0x4EAFA9C2), false)...)
    password = append(password, uint32ToBytes(uint32(0x00), false)...)
    password = append(password, uint32ToBytes(uint32(0x00), false)...)
    password = append(password, hostname[:]...)
    return password
}

func main() {
    password := get_whole_password()
    fmt.Printf("55个固定字节:%x", password)
}

  通过测试发现可以恢复这55个固定的字节,得出的数据与我们从勒索内存中所得的数据一样。但是因为要获取本机的用户名、CPU_ID和磁盘序列号,所以解密有一个前提就是只能在被加密的机器上运行才可以恢复。

  接着就是利用这55个字节来和程序自带的Public_Key进行Cure25519的密钥交换,生成user_public_key。

  先写一个Cure25519的密钥交换:

func get_key(private_key, public_key []byte) []byte {
    //// Curve25519: 计算共享密钥
    // 将公钥转换为 [32]byte 类型
    var publicKey [32]byte
    copy(publicKey[:], public_key)

    // 将私钥转换为 [32]byte 类型
    var privateKey [32]byte
    copy(privateKey[:], private_key[:])

    // Curve25519: 计算共享密钥
    var sharedKey [32]byte
    curve25519.ScalarMult(&sharedKey, &privateKey, &publicKey)
    return sharedKey[:]
}

  再来实现以下对应的生成:

hash := sha256.Sum256(password)
fmt.Printf("55个固定字符的Sha256:%x\n", hash)
public_key1, err := hex.DecodeString("CD20FA7A55F384AAFF27C1456636BD6374329E217B8066811A4F204591B8C1FF")
if err != nil {
    fmt.Println("hex to bytes error!")
}
share_key := get_key(hash[:], public_key1)
fmt.Printf("User_Public_Key:%x\n", share_key)

  接下来发现,已经能和勒索程序中的内容对应了。

  之后就是一系列的重复操作,最终可以恢复到share_key和user_public_key,然后利用user_public_key与程序末尾的user_public_key进行校验,如果一样,说明share_key可以用来解密,如果不一样,则说明该勒索文件并不是在本机上进行的加密行为。

  以上就是该破解的全部过程,后续就是正常的恢复算法等内容,比较简单,这里就不再继续了。

2.3 测试结果

2.3.1 恢复前

2.3.2 恢复后

视频

请看附件。

3. 病毒分析概览

  mallox家族的加密过程主要通过生成随机数种子、利用梅森旋转算法和对特定数据(如C盘序列号、CPUID等)进行SHA-256哈希处理,形成私钥和公钥,最终生成共享密钥来加密文件。文件加密过程中,采用了AES和ChaCha20等加密算法,并根据文件大小决定加密方式(完整加密或分块加密)。破解思路主要集中在获取随机数种子和分析55个固定字节的数据。

4.安全建议

1. 风险消减措施

  资产梳理排查目标: 根据实际情况,对内外网资产进行分时期排查

  服务方式: 调研访谈、现场勘查、工具扫描

  服务关键内容: 流量威胁监测系统排查、互联网暴露面扫描服务、技术加固服务、集权系统排查

2. 安全设备调优

目标

  通过对安全现状的梳理和分析,识别安全策略上的不足,结合目标防御、权限最小化、缩小攻击面等一系列参考原则,对设备的相关配置策略进行改进调优,一方面,减低无效或低效规则的出现频次;另一方面,对缺失或遗漏的规则进行补充,实现将安全设备防护能力最优化。

主要目标设备

  网络安全防护设备、系统防护软件、日志审计与分析设备、安全监测与入侵识别设备。

3. 全员安全意识增强调优

目标:

  通过网络安全意识宣贯、培训提升全方位安全能力

形式:

  培训及宣贯

线下培训课表

  若无法组织线下的集体培训,考虑两种方式:

    1.提供相关的安全意识培训材料,由上而下分发学习

    2.组织相关人员线上开会学习。线上培训模式。

线上学习平台

  以下是solar安全团队近期处理过的常见勒索病毒后缀:后缀.360勒索病毒,.halo勒索病毒,.phobos勒索病毒,.Lockfiles勒索病毒,.stesoj勒索病毒,.src勒索病毒,.svh勒索病毒,.Elbie勒索病毒,.Wormhole勒索病毒.live勒索病毒, .rmallox勒索病毒, .mallox 勒索病毒,.hmallox勒索病毒,.jopanaxye勒索病毒, .2700勒索病毒, .elbie勒索病毒, .mkp勒索病毒, .dura勒索病毒, .halo勒索病毒, .DevicData勒索病毒, .faust勒索病毒, ..locky勒索病毒, .cryptolocker勒索病毒, .cerber勒索病毒, .zepto勒索病毒, .wannacry勒索病毒, .cryptowall勒索病毒, .teslacrypt勒索病毒, .gandcrab勒索病毒, .dharma勒索病毒, .phobos勒索病毒, .lockergoga勒索病毒, .coot勒索病毒, .lockbit勒索病毒, .nemty勒索病毒, .contipa勒索病毒, .djvu勒索病毒, .marlboro勒索病毒, .stop勒索病毒, .etols勒索病毒, .makop勒索病毒, .mado勒索病毒, .skymap勒索病毒, .aleta勒索病毒, .btix勒索病毒, .varasto勒索病毒, .qewe勒索病毒, .mylob勒索病毒, .coharos勒索病毒, .kodc勒索病毒, .tro勒索病毒, .mbed勒索病毒, .wannaren勒索病毒, .babyk勒索病毒, .lockfiles勒索病毒, .locked勒索病毒, .DevicData-P-XXXXXXXX勒索病毒, .lockbit3.0勒索病毒, .blackbit勒索病毒等。

  勒索攻击作为成熟的攻击手段,很多勒索家族已经形成了一套完整的商业体系,并且分支了很多团伙组织,导致勒索病毒迭代了多个版本。而每个家族擅用的攻击手法皆有不同,TellYouThePass勒索软件家族常常利用系统漏洞进行攻击;Phobos勒索软件家族通过RDP暴力破解进行勒索;Mallox勒索软件家族利用数据库及暴力破解进行加密,攻击手法极多防不胜防。

  而最好的预防方法就是针对自身业务进行定期的基线加固、补丁更新及数据备份,在其基础上加强公司安全人员意识。如果您想了解有关勒索病毒的最新发展情况,或者需要获取相关帮助,请关注“solar专业应急响应团队”。

5.团队介绍

  团队坚持自主研发及创新,在攻防演练平台、网络安全竞赛平台、网络安全学习平台方面加大研发投入,目前已获得十几项专利及知识产权。团队也先后通过了ISO9001质量管理体系、ISO14000环境管理体系、ISO45001职业安全健康管理体系 、ITSS(信息技术服务运行维护标准四级)等认证,已构建了网络安全行业合格的资质体系;

6.我们的数据恢复服务流程

  多年的数据恢复处理经验,在不断对客户服务优化的过程中搭建了"免费售前+安心保障+专业恢复+安全防御"一体化的专业服务流程。

① 免费咨询/数据诊断分析

​   专业的售前技术顾问服务,免费在线咨询,可第一时间获取数据中毒后的正确处理措施,防范勒索病毒在内网进一步扩散或二次执行,避免错误操作导致数据无法恢复。

​   售前技术顾问沟通了解客户的机器中毒相关信息,结合团队数据恢复案例库的相同案例进行分析评估,初步诊断分析中毒数据的加密/损坏情况。

② 评估报价/数据恢复方案

​   您获取售前顾问的初步诊断评估信息后,若同意进行进一步深入的数据恢复诊断,我们将立即安排专业病毒分析工程师及数据恢复工程师进行病毒逆向分析及数据恢复检测分析。

​   专业数据恢复工程师根据数据检测分析结果,定制数据恢复方案(恢复价格/恢复率/恢复工期),并为您解答数据恢复方案的相关疑问。

③ 确认下单/签订合同

​   您清楚了解数据恢复方案后,您可自主选择以下下单方式:

  双方签署对公合同:根据中毒数据分析情况,量身定制输出数据恢复合同,合同内明确客户的数据恢复内容、数据恢复率、恢复工期及双方权责条款,双方合同签订,正式进入数据恢复专业施工阶段,数据恢复后进行验证确认,数据验证无误,交易完成。

④ 开始数据恢复专业施工

  安排专业数据恢复工程师团队全程服务,告知客户数据恢复过程注意事项及相关方案措施,并可根据客户需求及数据情况,可选择上门恢复/远程恢复。

  数据恢复过程中,团队随时向您报告数据恢复每一个节点工作进展(数据扫描 → 数据检测 → 数据确认 → 恢复工具定制 → 执行数据恢复 → 数据完整性确认)。

⑤ 数据验收/安全防御方案

  完成数据恢复后,我司将安排数据分析工程师进行二次检查确认数据恢复完整性,充分保障客户的数据恢复权益,二次检测确认后,通知客户进行数据验证。

  客户对数据进行数据验证完成后,我司将指导后续相关注意事项及安全防范措施,并可提供专业的企业安全防范建设方案及安全顾问服务,抵御勒索病毒再次入侵。

                      我们在此郑重承诺:

                     不成功不收费

                     全程一对一服务

                     365天不间断服务

                     免费提供安全方案

                     24h服务热线:

                     18894665383

                     17864099776

                     18299173318