样本来自于 vx-underground,此勒索软件相对比较简单。

分析

DIE 查询基本信息

是一个由 C# 编写的 32 位控制台程序。程序入口在 A.E.S.R.T.Program.Main 处,开始运行后在 C:\Program Files 下创建 Temp 目录及 Temp 下的 AESRT 子目录,随后提取自身资源并在 AESRT 目录下释放 AESRTback.png 和 refresh.bat 两个文件

AESRTback.png

refresh.bat

refresh.bat 内容意为强制刷新 Windows 的用户界面设置,例如主题、桌面、图标等 (在生成勒索信息框初始化组件会调用)。随后在 Software\Microsoft\Windows\CurrentVersion\Policies 下创建 System 和 ActiveDesktop 项,设置 Wallpaper、WallpaperStyle 和 NoChangingWallPaper 键及值

设置好后隐藏控制台,随即调用 Worm 类中的 Infectrandomusbfile() 方法,此操作会感染 USB 设备中的随机可执行文件 (随机选择一个文件并将其替换为当前正在运行的程序的副本)

最后调用 Encrypt 开始执行加密操作,在 Encrypt 中,删除系统中的所有卷影副本,设置系统启动时忽略所有失败,禁用启动恢复选项,删除备份目录

cmd.exe /c vssadmin delete shadows /all /quiet & wmic shadowcopy delete & bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quiet

随后在 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System 下设置 DisableTaskMgr 的值为 1,禁用任务管理器

在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 下修改 ShellExperience 的值为 ShellExperience.exe,此举更改了处理系统界面元素进程

注册表操作后,便开始加密以下 5 个位置中没有以 .AESRT 结尾的文件

  • %USERPROFILE%\Desktop;

  • %USERPROFILE%\Download;

  • %USERPROFILE%\Videos;

  • %USERPROFILE%\Music;

  • %USERPROFILE%\Pictures

每个位置下文件都是通过一个 for 循环进行加密操作,调用自身类中 EncryptFile 方法,采用的是 AES 加密,CBC 模式,将 password 转为字节序列,计算此字节序列的 SHA256 值,基于此 sha256 值生成 Key 及 IV 值

在完成加密后,设置注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System 中 EnableLUA 的值为 0,禁用 UAC,HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 下 Userinit 的值为 C:\windows\system32\userinit.exe,+自身程序所在路径,实现持久化,在 windows 启动时执行勒索软件本身。

最后生成勒索信息提示框,黑客邮箱:OfficialAESRT@outlook.de

Key 及 Password 都硬编码在程序中,Key 为 678123,Password 为 736457983546798345679547968257986453798346598732456934527986534798652467983546798234597682534976823456798967584,输入 Key 后执行 OFF_Encrypt 方法

修复系统启动

cmd.exe /c bootrec /rebuildbcd

根据此解密过程编写了一个通用解密器 (无需 Key),以下为解密器代码

import os
import argparse
import hashlib

from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Util.Padding import unpad

password = '736457983546798345679547968257986453798346598732456934527986534798652467983546798234597682534976823456798967584'

def aes_decrypt(bytes_to_be_decrypted, password_bytes):
    salt = bytes([1, 2, 3, 4, 5, 6, 7, 8])
    key_iv = PBKDF2(password_bytes, salt, dkLen=48, count=1000)
    key = key_iv[:32]
    iv = key_iv[32:48]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted_data = cipher.decrypt(bytes_to_be_decrypted)
    unpadded_data = unpad(decrypted_data, AES.block_size)
        
    return unpadded_data

def decrypt_file(filename):
    if filename.endswith('.AESRT'):
        print('[🌟] decrypting %s' % filename)
        with open(filename, 'rb') as f:
            encrypted_data = f.read()
            password_bytes = bytes.fromhex(hashlib.sha256(password.encode(encoding='utf-8')).hexdigest())
            decrypted_data = aes_decrypt(encrypted_data, password_bytes)
        with open(filename, 'wb') as f:
            f.write(decrypted_data)
        os.rename(filename, filename.rstrip('.AESRT'))
        print('[✅] %s decrypt success' % filename)
    else:
        print('[❌] %s not decryted file' % filename)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description='''AESRTRansomware Decryptor
                            by:zer0daysec''')
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-s', '--single', type=str, help='decrypt single file')
    group.add_argument('-t', '--table', type=str, help='decrypt entry table of contents')
    group.add_argument('-a', '--all', action='store_true', help='default decrypt file')
    args = parser.parse_args()

    if args.single:
        filename = args.single
        decrypt_file(filename)

    if args.table:
        table = args.table
        for file in os.listdir(table):
            file_path = os.path.join(table, file)
            if os.path.isfile(file_path):
                decrypt_file(file_path)

    if args.all:
        print('decrypting file ......')
        userprofile = os.getenv('USERPROFILE')
        contents = ['Desktop', 'Downloads', 'Videos', 'Music', 'Pictures']
        for content in contents:
            temp = userprofile + '\\' + content
            for file in os.listdir(temp):
                file_path = os.path.join(temp, file)
                if os.path.isfile(file_path):
                    decrypt_file(file_path)

yara

rule Win_Ransom_AESRT : Ransom AESRT
{
    meta:
        author = "zer0daysec"

        hash_1 = "05072a7ec455fdf0977f69d49dcaaf012c403c9d39861fa2216eae19c160527f"
        hash_2 = "b6743906c49c1c7a36439a46de9aca88b6cd40f52af128b215f808a406a69598"

    strings:
        $wallpaper = "AESRTback.png"
        $refresh = "refresh.bat"
        $email = "OfficialAESRT@outlook.de" wide
        $soglan = "Get Hacked By A.E.S.R.T" wide

    condition:
        uint16(0) == 0x5a4d and all of them
}

IoCs

HASH

  • b6743906c49c1c7a36439a46de9aca88b6cd40f52af128b215f808a406a69598

EMAIL

  • OfficialAESRT@outlook.de