检测范围与证据链修复,不改裁决口径(口径改动在下一条提交)。 - extractor:zip/tar 成员在超限分支外漏加回扫描队列,解包后内容永远不参与检测; zip 目录条目被当成内嵌对象;tar 超限成员先落盘不删。递归真正生效后深扫耗时可测。 - clamav:_HIGH/_MEDIUM/_UNWANTED 关键词是小写,而签名名是 "Win.Trojan..." 大小写混排, 分级判定恒不命中全走默认档;改统一大写匹配。超过 clamd MaxFileSize 的裸奔区间 (1G~1.5G) 新增 ClamAVSkippedSizeLimit(30) — 按"不可扫描不放行"进复核。 - yara:pe.yar C1 系列改用导入表判定,不再靠裸子串命中字符串表; linux.yar C7 多条规则条件恒真(裸 nc/fetch 之类)已收紧;C8/C3 相应收窄。 - structure:DangerousExecutableExtension 是载体信号而非恶意行为,critical 权重 45→35, 不再作为把中低置信度信号一起顶过 block_score 的底座。 - scanner/documents:real_type 拼进 libmagic 对 MSI/OLE 的元数据串实测 334+ 字符, 超列宽导致整条 scan_tasks INSERT 连 findings 一起丢(被拒文件不留痕)→ 列宽 256→512,migrate.sql 补 ALTER。
457 lines
18 KiB
Plaintext
457 lines
18 KiB
Plaintext
// =============================================================================
|
||
// MeshSafe L2B YARA 规则集 —— Windows PE 恶意程序专项(C1 + C8)
|
||
// =============================================================================
|
||
//
|
||
// 设计原则(v0.2.4 起,为消除干净软件误报而重写):
|
||
// 1. 全文搜 API 名 = 必然误报。系统 DLL 会「导出」这些 API(kernel32 里就有
|
||
// VirtualAllocEx/WriteProcessMemory/CreateRemoteThread 的明文字符串),
|
||
// 安全 SDK、安装器、调试组件同理。因此 C1 系列一律要求:
|
||
// - 文件是 PE(uint16(0) == 0x5A4D),且
|
||
// - 相关 API 名出现在同一个窗口内(导入名表是连续的,窗口 256~512B)。
|
||
// 导出表按字母序排列、跨度通常上 KB,落在窗口外 → 不误报;
|
||
// 真注入器/下载器的导入名表三条名字通常挨在一起 → 命中。
|
||
// 2. 不单独用「API 组合」定性,必须有硬指标(可疑路径 / 持久化键名 / C2 URL)。
|
||
// 3. 不用 3~4 字母裸串(曾经的 $persist5 = "RUN")和裸工具名。
|
||
// 4. binary.py 的 PE 结构分析负责节熵/签名/导入 API 分级;YARA 只做组合特征。
|
||
//
|
||
// 范围:
|
||
// - C1: Windows PE 恶意程序(注入器 / 下载器 / RAT / Loader)
|
||
// - C8: 勒索 / 挖矿专项(勒索样本常以文本/脚本形态投递,不加 PE 头判定)
|
||
// - C9: 壳特征(识别后建议送沙箱,不作为恶意证据)
|
||
//
|
||
// 维护:
|
||
// - 新增规则先跑「干净 PE 回归」:C:/Windows/System32 全量 900+ 文件必须 0 命中
|
||
// - meta.risk_level 会被 binary.py 读取用于严重度展示,必须与档位权重一致
|
||
// =============================================================================
|
||
|
||
import "pe"
|
||
|
||
|
||
// =============================================================================
|
||
// C1-1 进程注入三件套(VirtualAllocEx + WriteProcessMemory + CreateRemoteThread)
|
||
// 判定要点:三个导入名必须在同一张导入名表里(512B 窗口),否则是导出表明文
|
||
// =============================================================================
|
||
|
||
rule C1_PE_ProcessInjection {
|
||
meta:
|
||
risk_level = "critical"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1055 Process Injection"
|
||
description = "PE 导入表含进程注入三件套(同表紧邻)= 远控/注入器特征"
|
||
strings:
|
||
$alloc = "VirtualAllocEx" ascii
|
||
$write = "WriteProcessMemory" ascii
|
||
$thread = "CreateRemoteThread" ascii
|
||
// 进程镂空(Process Hollowing)
|
||
$unmap1 = "NtUnmapViewOfSection" ascii
|
||
$unmap2 = "ZwUnmapViewOfSection" ascii
|
||
// APC / Hook 注入
|
||
$apc = "QueueUserAPC" ascii
|
||
$hook = "SetWindowsHookEx" ascii
|
||
$open = "OpenProcess" ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and
|
||
(
|
||
// 经典三件套:同表紧邻
|
||
(
|
||
$alloc and $write and $thread
|
||
and for any i in (1..#alloc): (
|
||
$write in (@alloc[i] - 512..@alloc[i] + 512)
|
||
and $thread in (@alloc[i] - 512..@alloc[i] + 512)
|
||
)
|
||
)
|
||
or
|
||
// 镂空注入:unmap 必须和 write+thread 同表(kernel32/shell32 只是从
|
||
// ntdll 导入成对的 NtMapView/NtUnmap,没有写入+远线程就不算镂空注入)
|
||
(
|
||
$write and $thread
|
||
and for any i in (1..#unmap1): (
|
||
$write in (@unmap1[i] - 512..@unmap1[i] + 512)
|
||
and $thread in (@unmap1[i] - 512..@unmap1[i] + 512)
|
||
)
|
||
)
|
||
or
|
||
(
|
||
$write and $thread
|
||
and for any i in (1..#unmap2): (
|
||
$write in (@unmap2[i] - 512..@unmap2[i] + 512)
|
||
and $thread in (@unmap2[i] - 512..@unmap2[i] + 512)
|
||
)
|
||
)
|
||
or
|
||
// APC / Hook 注入:目标进程 API + 注入 API 同表紧邻
|
||
(
|
||
$alloc and $open and ($apc or $hook)
|
||
and for any i in (1..#alloc): (
|
||
$open in (@alloc[i] - 512..@alloc[i] + 512)
|
||
and ($apc in (@alloc[i] - 512..@alloc[i] + 512)
|
||
or $hook in (@alloc[i] - 512..@alloc[i] + 512))
|
||
)
|
||
)
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-2 下载器 / Loader(下载 API + 落地持久化键名,且两者同表紧邻)
|
||
// v0.2.3 的写法:任意下载 API + "WriteFile" + "CreateProcess" + 裸串 "RUN"
|
||
// → 任何带自动更新的正常软件必中。现在要求持久化硬指标。
|
||
// =============================================================================
|
||
|
||
rule C1_PE_Downloader_Loader {
|
||
meta:
|
||
risk_level = "high"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1105 Ingress Tool Transfer"
|
||
description = "下载 API 与自启动/服务注册持久化键名同表共存 = Loader"
|
||
strings:
|
||
// 下载类 API(保留专有名词,不用裸串)
|
||
$net1 = "URLDownloadToFile" ascii
|
||
$net2 = "InternetOpenUrl" ascii
|
||
$net3 = "WinHttpOpenRequest" ascii
|
||
$net4 = "WinHttpSendRequest" ascii
|
||
$net5 = "URLDownloadToCacheFile" ascii
|
||
$net6 = "FtpOpenFile" ascii
|
||
$net7 = "WinHttpOpen" ascii
|
||
$net8 = "InternetOpen" ascii
|
||
|
||
// 自启动 / 服务注册持久化硬指标(不是裸 "RUN")
|
||
$p1 = "Software\\Microsoft\\Windows\\CurrentVersion\\Run" ascii nocase
|
||
$p2 = "CurrentVersion\\RunOnce" ascii nocase
|
||
$p3 = "schtasks.exe" ascii nocase
|
||
$p4 = "CreateService" ascii
|
||
$p5 = "Start Menu\\Programs\\StartUp" ascii nocase
|
||
$p6 = "reg add" ascii nocase
|
||
$p7 = "StartupApproved" ascii nocase
|
||
|
||
// 临时目录落地(Loader 常先落 %TEMP%)
|
||
$tmp1 = /\\AppData\\Local\\Temp\\[a-z0-9_. -]{3,60}\.(exe|dll|tmp|dat)/ ascii nocase
|
||
$tmp2 = /%TEMP%\\[a-z0-9_. -]{3,60}\.(exe|dll|tmp)/ ascii nocase
|
||
$tmp3 = /\\Windows\\Temp\\[a-z0-9_. -]{3,60}\.(exe|dll|tmp)/ ascii nocase
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and any of ($net*)
|
||
and any of ($p*)
|
||
and
|
||
(
|
||
// 下载 API 与持久化键名同处一个导入/字符串表块
|
||
for any i in (1..#net1): ( any of ($p*) in (@net1[i] - 1024..@net1[i] + 1024) )
|
||
or (any of ($tmp*) and 2 of ($p*))
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-3 RAT / 远控特征
|
||
// v0.2.3 的写法用 send( / recv( / system( 这类通用符号,且
|
||
// (bot|serv|gate|ops)\.(com|net) 会匹配任意域名字符串 → 正常联网程序必中。
|
||
// 现在要求:C2 硬指标(IP:PORT、可执行下载 URL、家族标记)+ 套接字 API 同表。
|
||
// =============================================================================
|
||
|
||
rule C1_PE_RAT_CommandControl {
|
||
meta:
|
||
risk_level = "critical"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1071 Application Layer Protocol"
|
||
description = "RAT C2 特征:套接字 API + 明确 C2 指标(IP:端口 / .exe 下载 / 家族标记)"
|
||
strings:
|
||
// 套接字 API(不用裸 send/recv/connect)
|
||
$sock1 = "WSASend" ascii
|
||
$sock2 = "WSARecv" ascii
|
||
$sock3 = "WSAConnect" ascii
|
||
$sock4 = "WSASocket" ascii
|
||
$sock5 = "inet_ntoa" ascii
|
||
$sock6 = "getpeername" ascii
|
||
|
||
// 命令执行(真实 RAT 才会与 C2 同表出现)
|
||
$cmd1 = "CreateProcess" ascii
|
||
$cmd2 = "ShellExecute" ascii
|
||
$cmd3 = "WinExec" ascii
|
||
|
||
// C2 硬指标
|
||
$c2ip = /https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:[1-5][0-9]{3}/ ascii
|
||
$c2exe = /https?:\/\/[^\s"']{4,}\.(exe|dll|scr|vbs|ps1|bat)\b/ ascii nocase
|
||
$c2uri = /(gate|panel|submit|checkin|beacon|report)\.(php|aspx|html)\b/ ascii nocase
|
||
$c2tld = /\b[a-z0-9-]{4,30}\.(ru|top|xyz|tk|ml|ga|gq|cf|pw|buzz|loan)\b/ ascii nocase
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and 2 of ($sock*)
|
||
and any of ($c2*)
|
||
and
|
||
(
|
||
any of ($cmd*)
|
||
or $c2exe
|
||
or $c2uri
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-4 Mutex 互斥体名(勒索 / RAT / Loader 的持久化标记)
|
||
// 需要 CreateMutex 与家族 Mutex 名同表,避免文本文档里提一嘴家族名就命中
|
||
// =============================================================================
|
||
|
||
rule C1_PE_Mutex_Persistence {
|
||
meta:
|
||
risk_level = "medium"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1057 Process Discovery (mutex as persistence marker)"
|
||
description = "CreateMutex + 已知家族 Mutex 名同表 = 持久化标记"
|
||
strings:
|
||
$createmutex = "CreateMutexA" ascii
|
||
$createmutexw = "CreateMutexW" ascii
|
||
// LockBit
|
||
$lockbit1 = /Global\\[Ll]ock[Bb]it/ ascii
|
||
$lockbit2 = /Local\\[Ll]ock[Bb]it/ ascii
|
||
// BlackCat / ALPHV
|
||
$blackcat = /(Global|Local)\\[Bb]lack[Cc]at|\bALPHV\b/ ascii
|
||
// Ryuk
|
||
$ryuk = /RyukTask[\d]|Global\\Ryuk/ ascii
|
||
// Conti
|
||
$conti = /(Global|Local)\\[Cc]onti/ ascii
|
||
// Pseudo/Dharma
|
||
$dharma = /(Global|Local)\\[Dd]harma/ ascii
|
||
// 通用可疑模式:Mutex 名含 UUID 前缀
|
||
$generic1 = /Global\\Mutex[0-9a-f]{8}/ ascii
|
||
// Kryptik/Qakbot
|
||
$qakbot = /\b[Qq]akbot\b|\b[Kk]ryptik\b/ ascii
|
||
// Cobalt Strike named pipe
|
||
$cs1 = /\\\\\.\\pipe\\msagent_[0-9a-f]+/ ascii
|
||
$cs2 = /\\\\\.\\pipe\\[Bb]eacon_[0-9a-f]+/ ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and
|
||
(
|
||
any of ($cs*)
|
||
or (
|
||
any of ($createmutex*)
|
||
and any of ($lockbit1, $lockbit2, $blackcat, $ryuk, $conti, $dharma, $qakbot, $generic1)
|
||
)
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-5 DLL 劫持 / 侧加载
|
||
// 只看「LoadLibrary + GetProcAddress + 临时目录里的 DLL 名」三件套;
|
||
// \ProgramData\ 与 \Public\ 是正常安装器路径,不再单独作为可疑路径
|
||
// =============================================================================
|
||
|
||
rule C1_PE_DLLHijacking {
|
||
meta:
|
||
risk_level = "high"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1574 Hijack Execution Flow"
|
||
description = "动态加载 API + 临时目录 DLL 落地 = DLL 侧加载/劫持"
|
||
strings:
|
||
$loadlib = "LoadLibraryA" ascii
|
||
$loadlibw = "LoadLibraryW" ascii
|
||
$getproc = "GetProcAddress" ascii
|
||
$copy1 = "CopyFileA" ascii
|
||
$copy2 = "CopyFileW" ascii
|
||
$move = "MoveFileEx" ascii
|
||
$temp1 = /\\AppData\\Local\\Temp\\[a-z0-9_. -]{3,40}\.dll/ ascii nocase
|
||
$temp2 = /%TEMP%\\[a-z0-9_. -]{3,40}\.dll/ ascii nocase
|
||
$temp3 = /\\Windows\\Temp\\[a-z0-9_. -]{3,40}\.dll/ ascii nocase
|
||
$sysdir = "SetDllDirectory" ascii
|
||
$searchorder = "LoadLibraryEx" ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and any of ($loadlib*, $searchorder)
|
||
and any of ($temp*)
|
||
and (any of ($getproc, $copy1, $copy2, $move, $sysdir))
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-6 LOLBin(Living Off The Land Binary)调用特征
|
||
// 命令行串 + 进程创建 API 必须同表紧邻(正常程序会把两者写进同一个导入/资源块)
|
||
// =============================================================================
|
||
|
||
rule C1_PE_LOLBin_Usage {
|
||
meta:
|
||
risk_level = "high"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "https://lolbas-project.github.io/"
|
||
description = "PE 中出现 LOLBin 调用命令行 + 进程创建 API(同表)"
|
||
strings:
|
||
$lolbin1 = /certutil(\.exe)?\s+(-urlcache|-decode)\s+-split/ ascii nocase
|
||
$lolbin2 = /mshta(\.exe)?\s+(https?:|javascript:|vbscript:)/ ascii nocase
|
||
$lolbin3 = /regsvr32(\.exe)?\s+\/s\s+\/u:http/ ascii nocase
|
||
$lolbin4 = /bitsadmin(\.exe)?\s+\/transfer\s+\/source:/ ascii nocase
|
||
$lolbin5 = /wmic(\.exe)?\s+process\s+call\s+create/ ascii nocase
|
||
$lolbin6 = /msiexec(\.exe)?\s+\/[ia]\s+https?:/ ascii nocase
|
||
$lolbin7 = /installutil(\.exe)?\s+\/u\s+\/logfile=https?:/ ascii nocase
|
||
$lolbin8 = /psexec(\.exe)?\s+(-accepteula\s+)?-w\s+https?:/ ascii nocase
|
||
$exec1 = "CreateProcess" ascii
|
||
$exec2 = "ShellExecute" ascii
|
||
$exec3 = "WinExec" ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and any of ($lolbin*)
|
||
and any of ($exec*)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C1-7 PE 高熵 + 网络 API(疑似加壳下载器)
|
||
// 需要「多个网络 API + 明确壳节名」同时成立
|
||
// =============================================================================
|
||
|
||
rule C1_PE_EntropyHighNetwork {
|
||
meta:
|
||
risk_level = "medium"
|
||
category = "C1"
|
||
false_positive_rate = "low"
|
||
reference = "binary.py HighEntropySection 联动"
|
||
description = "PE 加壳节名 + 多个下载 API = 加壳下载器"
|
||
strings:
|
||
$net1 = "InternetOpenUrl" ascii
|
||
$net2 = "URLDownloadToFile" ascii
|
||
$net3 = "WinHttpOpenRequest" ascii
|
||
$net4 = "WSAConnect" ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and 2 of ($net*)
|
||
and (
|
||
for any i in (0..pe.number_of_sections - 1): (
|
||
pe.sections[i].name matches /UPX0/ or pe.sections[i].name == "UPX1"
|
||
or pe.sections[i].name matches /\.vmp[0-9]/ or pe.sections[i].name matches /Themida|\.themida/
|
||
)
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C8-1 勒索加密链(加密 API + 卷影删除/勒索信,勒索样本常以文本形态投递,不加 PE 头)
|
||
// =============================================================================
|
||
|
||
rule C8_Ransomware_Encryption {
|
||
meta:
|
||
risk_level = "critical"
|
||
category = "C8"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1486 Data Encrypted for Impact"
|
||
description = "勒索加密链:加密 API + 卷影删除命令或勒索信文件名"
|
||
strings:
|
||
// 加密 API
|
||
$enc1 = "CryptEncrypt" ascii
|
||
$enc2 = "CryptDeriveKey" ascii
|
||
$enc3 = "BCryptEncrypt" ascii
|
||
$enc4 = "BCryptDeriveKeyPair" ascii
|
||
$enc5 = "CryptGenKey" ascii
|
||
|
||
// 文件遍历(全盘加密需要)
|
||
$enum1 = "FindFirstFileA" ascii
|
||
$enum2 = "FindFirstFileW" ascii
|
||
$enum3 = "FindNextFileA" ascii
|
||
$enum4 = "FindNextFileW" ascii
|
||
|
||
// 卷影删除(完整命令,不认裸 "wmic")
|
||
$vss1 = "vssadmin" ascii nocase
|
||
$vss2 = "delete shadows" ascii nocase
|
||
$vss3 = "DeleteShadowCopies" ascii
|
||
$vss4 = "shadow copies" ascii nocase
|
||
|
||
// 勒索信文件名 / 加密扩展名
|
||
$note1 = /(HOW_TO_RECOVER|RESTORE_FILES|DECRYPT_INSTRUCTIONS|HELP_DECRYPT)[\w.-]*\.(txt|html|png|jpg|bmp|url)/ nocase
|
||
$note2 = /\.(locky|zepto|cryptolocker|crpt|encrypted|pay2)\b/ nocase
|
||
condition:
|
||
any of ($enc*)
|
||
and
|
||
(
|
||
($vss1 and ($vss2 or $vss3 or $vss4))
|
||
or (2 of ($enum*) and any of ($note*))
|
||
or (2 of ($enc*) and any of ($enum*) and $vss1)
|
||
)
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C8-2 挖矿(xmrig / stratum+tcp / 矿池域名)
|
||
// =============================================================================
|
||
|
||
rule C8_Mining_Monero {
|
||
meta:
|
||
risk_level = "high"
|
||
category = "C8"
|
||
false_positive_rate = "low"
|
||
reference = "MITRE ATT&CK T1496 Resource Hijacking"
|
||
description = "挖矿程序特征:xmrig 命令行参数 / stratum+tcp / 矿池域名"
|
||
strings:
|
||
$xmrig1 = "xmrig" ascii nocase
|
||
$xmrig2 = /--donate-level\s+\d+/ ascii
|
||
$xmrig3 = /-o\s+stratum\+tcp/ ascii
|
||
$xmrig4 = /--cpu-priority\s+\d/ ascii
|
||
$xmrig5 = /--max-threads-hint/ ascii
|
||
$pool1 = "pool.xmrig.com" ascii nocase
|
||
$pool2 = "pool.minexmr.com" ascii nocase
|
||
$pool3 = "pool.supportxmr.com" ascii nocase
|
||
$pool4 = "mine.moneropool.com" ascii nocase
|
||
$pool5 = "stratum+tcp://" ascii
|
||
$thread = "CreateThread" ascii
|
||
condition:
|
||
any of ($pool1, $pool2, $pool3, $pool4)
|
||
or ($pool5 and (any of ($xmrig*) or $thread))
|
||
or (2 of ($xmrig*) and any of ($pool*))
|
||
}
|
||
|
||
|
||
// =============================================================================
|
||
// C9 壳特征(UPX / Themida / VMProtect)
|
||
// 加壳本身不是恶意证据(大量开源工具用 UPX),只作沙箱深检触发条件
|
||
// =============================================================================
|
||
|
||
rule C9_Packer_UPX {
|
||
meta:
|
||
risk_level = "low"
|
||
category = "C9"
|
||
false_positive_rate = "low"
|
||
reference = "UPX Packer"
|
||
description = "UPX 加壳 PE:节名 UPX0/UPX1 + UPX 签名"
|
||
strings:
|
||
$upx2 = "UPX!" ascii
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and (
|
||
(
|
||
for any i in (0..pe.number_of_sections - 1): (
|
||
pe.sections[i].name matches /UPX0/
|
||
)
|
||
and for any j in (0..pe.number_of_sections - 1): (
|
||
pe.sections[j].name matches /UPX1/
|
||
)
|
||
)
|
||
or ($upx2 and pe.number_of_sections <= 4)
|
||
)
|
||
}
|
||
|
||
rule C9_Packer_Themida_VMP {
|
||
meta:
|
||
risk_level = "medium"
|
||
category = "C9"
|
||
false_positive_rate = "low"
|
||
reference = "Themida / VMProtect"
|
||
description = "商业壳特征:Themida / VMProtect 节名 + 产品标识字符串"
|
||
strings:
|
||
$themida1 = "Themida" ascii wide
|
||
$themida2 = "WinLicense" ascii wide
|
||
$themida3 = "Oreans" ascii wide
|
||
$vmp1 = "VMProtect" ascii wide
|
||
condition:
|
||
uint16(0) == 0x5A4D
|
||
and (
|
||
for any i in (0..pe.number_of_sections - 1): (
|
||
pe.sections[i].name matches /\.themida|\.vmp[0-9]/
|
||
)
|
||
or (any of ($themida*) and any of ($vmp1))
|
||
)
|
||
}
|