样本的文件

来源: https://bbs.360.cn/thread-16185961-1-1.html

白加黑:白进程 360speedld.exe,黑 DLL somkernl.dll。

黑 DLL 内部名是 proram_c_nocrt_agent_failover_dll.dll,构建标记 clash-silent-speedld-dll。导出故意对齐系统库 msimg32.dll(AlphaBlend / GradientFill / TransparentBlt / vSetDdrawflag),真正干活的是多出来的 SomPlugin。五个绘图导出会 LoadLibraryA("C:\\Windows\\SysWOW64\\msimg32.dll") 再 jmp 过去,白程序界面不崩。

也可以改名叫 msimg32.dll 放白进程旁边做侧加载,或者保持 somkernl.dll 当 360 插件被调 SomPlugin。

下面先看黑 DLL。

行为分析

目标是一套 C2 Agent(PRO_RAM C NoCRT Agent/0.2),进白进程后上线。

DllMain / SomPlugin
  |- 宿主校验 sub_1001A7D4
        |- 通过:sub_1001A712 心跳线程 + start_agent_thread
        |- 不通过:挂起拉起白进程,注入自己,设 PRORAM_SL_KEEP=1
              |- start_agent_thread          // CAS 只起一次
                   |- agent_main_thread
                        |- agent_main        // 等价 main
                             |- 解析命令行
                             |- create_instance_mutex -> build_signed_hello
                             |- agent_connect_loop   // WSS / DNS 上线

SomPlugin 做完同样启动后,如果宿主是 360speedld.exe,会 Sleep(60000) 死循环,插件调用不返回,DLL 不容易被卸。

DllEntryPoint

加载 DLL 时先把函数名改清楚再往下看。DllMain 在 DLL_PROCESS_ATTACH 里大致是:

  1. DisableThreadLibraryCalls
  2. sub_1001A7D4:当前进程能不能直接上线
  3. 能:sub_1001A712 + start_agent_thread(原来的 sub_1001A5E8)
  4. 不能:记下自身路径,挂起创建白进程再注入,并设置 PRORAM_SL_KEEP=1,避免递归

sub_1001A7D4 不是反沙箱,是宿主白名单。满足任一条件就返回 1:

int sub_1001A7D4()
{
    if (sub_1001A7F4())   // PRORAM_SL_KEEP == "1"
        return 1;
    if (sub_1001A75E())   // 进程名是 360speedld.exe
        return 1;
    return sub_1001A752(); // 进程名是 fixmbr.exe
}

sub_1001A712 也不是藏窗口。只有宿主已经是 360speedld.exe 或 fixmbr.exe 时,CAS 一次,起心跳线程 sub_1001AAA1。

HANDLE sub_1001A712()
{
    HANDLE result;
    if (sub_1001A752() || (result = (HANDLE)sub_1001A75E()) != 0)
    {
        result = (HANDLE)_InterlockedCompareExchange((volatile signed __int32 *)&unk_10021028, 1, 0);
        if (!result)
        {
            result = CreateThread(0, 0, sub_1001AAA1, 0, 0, 0);
            if (result)
                return (HANDLE)CloseHandle(result);
        }
    }
    return result;
}

sub_1001A5E8 才是起 Agent 主线程(start_agent_thread),不是 agent_main 本身:

HANDLE start_agent_thread()   // sub_1001A5E8
{
    HANDLE result;
    result = (HANDLE)_InterlockedCompareExchange(&dword_10021020, 1, 0);
    if (!result)
    {
        result = CreateThread(0, 0, agent_main_thread, 0, 0, 0);
        if (result)
            return (HANDLE)CloseHandle(result);
    }
    return result;
}

agent_main_thread 进去直接调 agent_main,最后 exit,不回到 DLL。

agent_main

这是 C2 Agent 的 main。按顺序:

准备内存

一块命令行 0x800,一块运行时配置 cfg 0xC00。失败则 exit(2)。

灌默认配置

load_embedded_config 把 .prcfg 里的 C2、build、版本填进 cfg+0x400。

读命令行

用的是 cmdline_has_flag,不是整串 strcmp。

  • --print-capabilities:打印能力列表后 exit(0)
  • --once:只连一次
  • --insecure-tls:不校 TLS
  • --server URL:覆盖 C2,命令行地址顶掉内嵌地址,后面主循环连这个

没有 --server 时:先环境变量 PRORAM_SERVER_URL,再没有才用内嵌 URL。

单实例

create_instance_mutex(sub_10015B32)。互斥量已存在打 duplicate_instance,exit(0x11)。

进入主循环

agent_connect_loop 走 WSS / DNS failover。循环返回后把返回码交给 exit。

--print-capabilities 打出来的能力:

​

void print_capabilities_and_exit()
{
    print_stdout(
        "core.transport.failover,core.transport.websocket,core.transport.dns,"
        "core.identity.signed_hello,core.heartbeat.ping_pong,core.protocol.msgpack,"
        "core.transfer.file_upload_v1,system_info.get,diagnostics.get,shell.exec,"
        "file_manager.list_dir,file_manager.stat,file_manager.read_small_file,"
        "file_manager.download_file,file_manager.upload,file_manager.create_file,"
        "file_manager.mkdir,file_manager.rename,file_manager.delete,"
        "persistence.check,persistence.install,persistence.uninstall");
    exit(0);
}

对应:WSS/DNS 双通道、signed hello、心跳、msgpack、shell、文件管理、HKCU Run 持久化(值名 ProRAMAgent)。导入里还能看到远程线程、写内存、reverse socks / 截屏相关痕迹。

​

create_instance_mutex(sub_10015B32)

这里会生成签名,但本函数不验签。

流程:

  1. 调 build_signed_hello("PRORAM_INSTANCE_LOCK_NONCE", build, hello),算出整份 0x180 字节 HELLO(里面带着 Ed25519 签名)。
  2. 从 HELLO 的 fingerprint 里取出 "sha256:" 后面前 16 个 hex(公钥指纹,不是 signature 字段)。
  3. 拼名字:Global\ProRAM-Agent- + 这 16 hex。
  4. CreateMutexA。GetLastError()==0xB7(ERROR_ALREADY_EXISTS)就认为已经有实例。

同一份 identity.bin → 同一把锁,只能跑一个 Agent。签名字段 hello+0x120 这里看都不看。

build_signed_hello

真正生成签名的地方。不联网。ecx=nonce,edx=build,输出 0x180 字节:

偏移

字段

内容

+0x00

agent_id

agent_c_ + 公钥 SHA256 前 16 hex(可用 PRORAM_AGENT_ID 覆盖)

+0x60

public_key

Ed25519 公钥 Base64

+0x90

fingerprint

"sha256:" + 公钥哈希

+0xD8

host_fingerprint

MachineGuid / 计算机名 / USERDOMAIN / 卷序列号

+0x120

signature

对 hello 明文的签名再 Base64


算法:

  1. 32 字节身份种子PRORAM_AGENT_SEED_HEX / PRORAM_AGENT_IDENTITY_SEED_HEX(64 hex),否则读/创建 identity.bin(CryptGenRandom 后落盘)。build 只参与目录名,例如 %LOCALAPPDATA%\ProRAM\instances\portable-<16hex>\identity.bin。
  2. 种子 → Ed25519 公钥(clamp 后乘基点)。
  3. 拼明文并签名"PRO_RAM_AGENT_HELLO_V1:" + nonce,私钥签 64 字节,再 Base64 写入 +0x120。

WSS 上线时把整份 HELLO(公钥 + signature + agent_id 等)当 signed_hello 发给 C2,对端用公钥验签。本 DLL 里只负责生成和组包,没有本地验签。

​

C2 和落地

  • 传输:WSS 主通道 + DNS failover,协议 msgpack,心跳 ping/pong。
  • 持久化:HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ProRAMAgent。
  • 身份目录:%LOCALAPPDATA%\ProRAM\instances\ 或 %ProgramData%\ProRAM\instances\,文件 identity.bin(32 字节)。
  • 互斥量:Global\ProRAM-Agent-*。
  • 环境变量前缀 PRORAM_:PRORAM_SERVER_URL、PRORAM_SL_KEEP、PRORAM_AGENT_ID、PRORAM_C_AGENT_LOG 等。

检测可以看:360speedld.exe 从非官方目录加载 msimg32.dll / somkernl.dll;导出同时有 AlphaBlend 和 SomPlugin;节名 .prcfg 以 PRORAMCFGv2 开头。