火绒安全软件

火绒安全播报
发新帖
打印 上一主题 下一主题

[分析报告] "银狐"变种: 技术分析揭穿病毒多层混淆虚拟化伪装(五)

[复制链接]
407 0
楼主
发表于 2025-9-26 22:51:43 | 只看该作者 |倒序浏览 |阅读模式
跳转到指定楼层
本帖最后由 huoronganquan 于 2025-9-27 09:45 编辑


创建COM对象代码图


首先通过IWbemLocator->ConnectServer创建指定的计算机上的 WMI 命名空间的连接得到IWbemServices指针,然后通过IWbemServices->ExecQuery执行WQL语句获取计算机敏感信息,返回的信息存储到IEnumWbemClassObject指针中, 后续通过IEnumWbemClassObject->next方法遍历返回的对象IWbemClassObject的指针,最后通过IWbemClassObject->get方法获取返回对象的属性SerialNumber,该属性值可以用于区分和识别磁盘驱动器。
通过上述代码的执行,该程序可以获取到主机的硬件信息唯一地标识该计算机。
[3] 感染载荷释放后续载荷所需文件
(1) 释放文件
接下来, 病毒开始释放后续文件。


病毒释放文件路径构建代码图






病毒首先获取用户"Documents"目录路径, 构建恶意目录名称, 将释放的恶意载体的名称并以系统+隐藏模式创建目标目录。


病毒释放文件路径构建代码图
其中, 构建得到的目录名为<随机8字节>, exe文件名称为<随机6字节>.exe, 其他文件的名称固定为"npwzwmc64.dll", "space.ico", "vdi_ipc.dat"。

病毒释放文件示意图
接着, 病毒读取自身文件, 从中寻找标志数据"QEMB8WGP", 然后读取标志文本后的4个字节作为应提取的数据大小。
寻找标志数据代码图

对应数据大小图
将数据提取后, 病毒再读入自身PE头中的时间戳数据, 将其作为异或密钥, 以8位解密提取的数据。

时间戳作为密钥解密提取数据代码图
按照规则编写解密python代码如下:




然后, 病毒读取在DllMain中被初始化的数据指针, 解引用后得到的数据, 以104为异或密钥进行8bit-xor解密, 将得到8个标志数据, 其中每个标志数据的前16字节标志着4个待提取数据的首尾字节串(<1,0>,<3,2>,<5,4>,<7,6>)。

通过标志查找到的对应数据示意图
这里可以编写Python代码对特征块进行解密:

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-

  3. key = 0x68                                      # 104 -> 0x68

  4. raw_hex = [
  5.     # 0x545E00 + 0x545E10
  6.     "eb ec e7 c9 d7 d0 dd d0 ca c4 91 93 89 c3 cb cb"
  7.     " a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7",

  8.     # 0x548B60 + 0x548B70
  9.     "84 ac ac c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 f7 92 c0"
  10.     " 6c 00 00 00 00 00 00 00 68 e7 c9 be 00 3b 00 8e",

  11.     # 0x546160 + 0x546170
  12.     "eb ec e7 d4 d7 c6 c4 c2 89 ce c4 c8 a7 a7 a7 a7"
  13.     " a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7",

  14.     # 0x548720 + 0x548730
  15.     "89 a3 af c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 e0 0e"
  16.     " 00 00 00 00 00 00 00 00 ac e7 85 be 00 19 00 90",

  17.     # 0x545C20 + 0x545C30
  18.     "eb ec e7 d1 c3 ce f8 ce d7 c4 89 c3 c6 d3 a7 a7"
  19.     " a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7",

  20.     # 0x5489C0 + 0x5489D0
  21.     "84 a1 b4 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 a1 a7 95"
  22.     " 6c 00 6c 00 00 00 00 00 42 e7 d3 be 00 2e 00 88",

  23.     # 0x546190 + 0x5461A0
  24.     "eb ec e7 d1 ce d2 d4 c4 d3 d5 ce d1 ce c6 cb 89"
  25.     " d4 de d4 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7 a7",

  26.     # 0x548440 + 0x548450
  27.     "93 b9 b3 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 ae b0"
  28.     " 00 00 00 00 00 00 00 00 9a e7 bb be 00 02 00 88",
  29. ]

  30. # ── 解密并打印 ───────────────────────────────────────────────────
  31. for idx, line in enumerate(raw_hex, 1):
  32.     # 把连续字符串转成 bytes
  33.     cipher = bytes.fromhex(line)
  34.     plain  = bytes(b ^ key for b in cipher)

  35.     print(f'Block #{idx}:')
  36.     for i in range(0, len(plain), 16):
  37.         chunk = plain[i:i+16]
  38.         print('  ' + ' '.join(f'{b:02x}' for b in chunk))
  39.     print()
复制代码

代码运行结果示意图

接下来编写代码对对应的数据进行提取:
  1. import struct
  2. import os

  3. def encrypt_data(data):
  4.     key = (-1775093251) & 0xFFFFFFFF
  5.     encrypted = bytearray(data)
  6.      
  7.     for i in range(len(encrypted)):
  8.         # 获取当前字节
  9.         original_byte = encrypted[i]
  10.          
  11.         # 计算 XOR 密钥字节 (key >> (8 * (i % 4))) & 0xFF
  12.         shift_amount = 8 * (i % 4)
  13.         key_byte = (key >> shift_amount) & 0xFF
  14.          
  15.         # XOR 加密
  16.         encrypted[i] = original_byte ^ key_byte
  17.          
  18.         key = (original_byte | (key << 8)) & 0xFFFFFFFF
  19.      
  20.     return bytes(encrypted)

  21. def read_and_encrypt_ranges(input_file, output_files, ranges):
  22.     if not os.path.exists(input_file):
  23.         print(f"错误: 找不到输入文件 {input_file}")
  24.         return False
  25.      
  26.     # 获取文件大小
  27.     file_size = os.path.getsize(input_file)
  28.     print(f"输入文件大小: {file_size} bytes (0x{file_size:X})")
  29.      
  30.     try:
  31.         with open(input_file, 'rb') as f:
  32.             for i, (start, end, output_file) in enumerate(zip([r[0] for r in ranges],  
  33.                                                               [r[1] for r in ranges],  
  34.                                                               output_files)):
  35.                 # 验证范围
  36.                 if start >= file_size:
  37.                     print(f"警告: 范围 {i+1} 的起始位置 0x{start:X} 超出文件大小")
  38.                     continue
  39.                  
  40.                 if end > file_size:
  41.                     print(f"警告: 范围 {i+1} 的结束位置 0x{end:X} 超出文件大小,将调整为文件末尾")
  42.                     end = file_size
  43.                  
  44.                 # 读取数据
  45.                 f.seek(start)
  46.                 data_length = end - start
  47.                 data = f.read(data_length)
  48.                  
  49.                 if len(data) != data_length:
  50.                     print(f"警告: 范围 {i+1} 只能读取 {len(data)} bytes,预期 {data_length} bytes")
  51.                  
  52.                 print(f"处理范围 {i+1}: 0x{start:X} - 0x{end:X} ({len(data)} bytes)")
  53.                  
  54.                 # 加密数据
  55.                 encrypted_data = encrypt_data(data)
  56.                  
  57.                 # 写入输出文件
  58.                 with open(output_file, 'wb') as out_f:
  59.                     out_f.write(encrypted_data)
  60.                  
  61.                 print(f"  已写入到 {output_file}")
  62.                  
  63.                 # 显示前几个字节用于验证
  64.                 if len(data) >= 16:
  65.                     print(f"  原始数据前16字节: {data[:16].hex()}")
  66.                     print(f"  加密数据前16字节: {encrypted_data[:16].hex()}")
  67.          
  68.         return True
  69.          
  70.     except Exception as e:
  71.         print(f"错误: {e}")
  72.         return False

  73. def main():
  74.     input_file = "output.bin"
  75.     output_files = ["1.bin", "2.bin", "3.bin", "4.bin"]

  76.     ranges = [
  77.         (0x616799, 0x98B999),
  78.         (0x9A74A9, 0x9A9567),
  79.         (0x24, 0x616769),
  80.         (0x9A9597, 0x9B0407)
  81.     ]
  82.      
  83.     print("文件解密工具")
  84.     print("=" * 50)
  85.     print(f"输入文件: {input_file}")
  86.     print(f"输出文件: {', '.join(output_files)}")
  87.     print()
  88.      
  89.     print("处理范围:")
  90.     for i, (start, end) in enumerate(ranges):
  91.         size = end - start
  92.         print(f"  范围 {i+1}: 0x{start:06X} - 0x{end:06X} (大小: {size} bytes, {size/1024/1024:.2f} MB)")
  93.     print()
  94.      
  95.     if read_and_encrypt_ranges(input_file, output_files, ranges):
  96.         print("\n解密完成!")
  97.     else:
  98.         print("\n解密过程中出现错误")

  99. if __name__ == "__main__":
  100.     main()
复制代码


提取后, 得到对应的数据。

提取加密后的数据代码图
随后,病毒对数据进行解密处理后写入磁盘,同时会将首个exe(可执行文件)末尾的 1 字节替换为随机字节,将第二个文件末尾倒数第 4 字节替换为随机字节,将第三、四个文件的第 0xF 字节替换为随机字节,以此规避通过简单哈希值实施的检测。


病毒更改文件哈希代码图
而且此处会判断之前检测安全软件时写入的标志位, 如果检测到过有安全软件正在运行, 则不会关闭文件句柄, 使得文件一直在"占用"状态。

病毒根据是否有安全软件来决定是否关闭文件代码图
病毒将四个文件释放后, 对其目录及文件设置隐藏, 然后释放第五个文件。第五个文件被释放在目录C:\Windows\Temp下, 文件名固定为ranchserv.jpg。


将文件写到固定目录代码图

对应文件图
然后病毒读取自身文件, 在自身的文件内存中搜索"%&$6@=*"。

在自身文件内存中搜索示意图


对应数据图
将搜索到的数据前方添加32字节的随机数据, 然后一起写入到HKLM\SOFTWARE\JDBCC\data中。

对应注册项数据图
随后, 病毒首先判断
C:\Users\<用户名>\Documents\<病毒目录>\<exe文件名称>.exe
C:\Users\<用户名>\Documents\<病毒目录>\<ico文件名称>.ico
两份文件是否存在, 如果不存在, 则其重启自身, 再次执行上述的全部感染载荷执行的感染步骤。如果还存在, 则开始执行持久化。







回复

使用道具 举报

您需要登录后才可以回帖 登录 | [立即注册]

本版积分规则

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