Files
MeshSate/rules/pe.yar
zhaolei b6a004bdb7 fix: 六引擎误报根因修复 — 解包递归、YARA 恒真规则、签名分级、real_type 溢出
检测范围与证据链修复,不改裁决口径(口径改动在下一条提交)。

- 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。
2026-11-02 20:26:56 +08:00

457 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// =============================================================================
// 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))
)
}