# moonpe 架构说明

## 1. 目标与边界

moonpe 只做一件事：**把一份 PE 映像的字节变成可查询的结构**。它不执行、不反汇编、
不验证签名、不改写文件。所有能产生判断的逻辑（`check`）都建立在这些结构之上，
不依赖外部工具。

## 2. 模块地图

| 文件 | 职责 | 依赖 |
| --- | --- | --- |
| `error.mbt` | `PeError`、十六进制格式化 | 无 |
| `reader.mbt` | 带边界检查的小端读取器 | `error` |
| `headers.mbt` | COFF 头、可选头、数据目录、机器/子系统名称 | `error` |
| `sections.mbt` | 节表与权限判定 | `headers` |
| `pe.mbt` | `Pe::parse`、RVA 映射、节字节 | 上述全部 |
| `imports.mbt` | 导入表（名字/序号） | `pe` |
| `iat.mbt` | 静态/延迟 IAT 逐槽映射（库、RVA、原始值、符号） | `pe`、`imports`、`delay_imports` |
| `delay_imports.mbt` | 延迟导入（RVA / VA 两种描述符） | `pe`、`imports` |
| `exports.mbt` | 导出表（命名 + 仅序号） | `pe` |
| `debug.mbt` | 调试目录、CodeView `RSDS`/`NB10` | `pe` |
| `relocations.mbt` | 重定位块统计 | `pe` |
| `resources.mbt` | 资源树、VERSIONINFO | `pe` |
| `resource_content.mbt` | STRING、位图、图标、图标组、清单内容 | `pe`、`resources` |
| `directories.mbt` | 16 个目录的命名与映射 | `pe` |
| `tls.mbt` | TLS 目录与回调计数 | `pe` |
| `clr.mbt` | CLR 头与元数据版本串 | `pe` |
| `metadata.mbt` | .NET 元数据流、表行数、程序集/类型/方法 | `pe` |
| `manifest.mbt` | 应用清单与 UAC 级别 | `pe`、`resources` |
| `exception.mbt` | 运行时函数表（x64/ARM64） | `pe` |
| `bound_imports.mbt` | 绑定导入模块名 | `pe` |
| `entropy.mbt` | Shannon 熵（原地扫描） | `math` |
| `check.mbt` | 体检报告（finding + verdict） | 上述全部 |
| `derives.mbt` | 显式方法提升声明 | `debug` |
| `cmd/moonpe` | 单文件命令、JSON 报告、递归扫描（唯一做 IO 的地方） | `x/fs`、`x/path` |

库包本身只依赖 core；`moonbitlang/x` 只被 CLI 和示例使用。

## 3. 数据流

```
Bytes
  └─ Pe::parse
       ├─ DOS 头 → e_lfanew
       ├─ NT 签名 → COFF 头
       ├─ 可选头（PE32 / PE32+）→ 数据目录
       └─ 节表
            └─ Pe（持有原始字节 + 已解析头）
                 ├─ rva_to_offset(rva) → 文件偏移
                 ├─ imports() / iat() / delay_imports() / exports()
                 ├─ debug_entries() / relocations() / resources()
                 ├─ decode_resource() / tls() / clr() / clr_metadata()
                 ├─ manifest() / function_table()
                 └─ check()
```

表是按需解析的：`Pe::parse` 只读头部和节表，导入导出在调用时才走。
这样 `info` 不必为 1697 个导出付出代价，而 `all` 只走一次。

## 4. 错误模型

`PeError` 四个变体：`OutOfBounds(offset, need, have)`、`BadMagic(what, offset)`、
`Malformed(what, offset)`、`Unsupported(what)`。每个结构错误都带字节偏移，
调用方可以直接定位到出问题的那几个字节。

解析器不做"尽力而为"：遇到结构不一致直接报错。半解析出来的导入表比明确失败更危险。

## 5. 三条不变量

1. **任何读取都先过 `need`**，不存在未检查的索引。
2. **RVA 映射必须拒绝节内零填充尾部**：`VirtualSize > RawSize` 的那段在文件里不存在。
3. **32 位字段用 `Int64` 读、再查范围**；纯常量（如 `VS_FIXEDFILEINFO` 签名）走
   `u32_raw`，因为它不是地址。

## 6. 性能

`section_entropy` 在原始缓冲区上原地统计，不复制节内容——一个被打包的大节可能有几百 MB，
复制一遍就会主导整个扫描时间。`section_bytes` 仍然复制，因为它是给调用方拿数据的显式 API。

实测（本机，Node 启动计入墙钟）：

| 文件 | 大小 | `check` | `all` |
| --- | --- | --- | --- |
| `shell32.dll` | 7.64 MB | 0.46 s | 0.33 s |
| `kernel32.dll` | ~1.4 MB | < 0.3 s | < 0.3 s |

## 7. 测试策略

- **手工样本**：PE32 / PE32+ 全可选头字段、导入与静态/延迟 IAT（4/8 字节槽）、延迟导入
  （两种描述符）、导出、调试、重定位、资源、VERSIONINFO、TLS、CLR、清单、
  函数表、绑定导入、.NET 元数据表以及常见资源内容。
- **畸形输入**：缺 `MZ`、缺 `PE\0\0`、截断、`e_lfanew` 越界、节表超量。
- **穷举截断**：把合法样本截断成每一种长度，要求全部报错。
- **变异模糊**：500 轮确定性改写；每个仍能解析的样本都会调用全部公开解析接口。
- **真实语料**：`kernel32`（PE32 与 PE64）、`shell32`、`notepad`、`mscorlib`、
  `System.Core`，以及 Windows PowerShell 目录下 59 个 PE 的递归扫描。

## 8. 已知限制

- 不执行、不反汇编、不验证 Authenticode 签名（只报告证书表是否存在）。
- .NET 可读元数据流、表行数、程序集身份、引用、类型与方法；不解码 IL 指令。
- 资源可读 STRING、位图/图标尺寸、图标组、清单和 VERSIONINFO；不渲染图标像素。
- Exception 提供函数表条目数与 RVA 跨度；BoundImport 提供模块名；
  Architecture、GlobalPtr 与 Reserved 按 PE 规范本身没有可进一步解释的目录语义。
  Security 解析证书条目但不验签；IAT 已逐槽映射，不再只报告目录地址。
