docs: DLP 环境特征调研记录与判定/写入逻辑说明

- docs/DLP_ENVIRONMENT_NOTES.md: 白名单矩阵/密文样本/属性不可区分等实测结论与探索过程
- README: DLP 程序化特征与 is_encrypted/写入通道实现逻辑摘要
This commit is contained in:
p40000043244@byd.com
2026-07-31 18:32:14 +08:00
parent 7b62c23e1e
commit 457e4eed3f
3 changed files with 176 additions and 1 deletions
+150
View File
@@ -0,0 +1,150 @@
# DLP 环境特征调研与检测/写入实现逻辑
本文记录在本机(启用透明加密 DLP 的 Windows)上对 `tests/DLP加密文件.pptx`
`tests/DLP解密文件.pptx` 两个样本的实测结论、探索过程,以及 `dlp_io` 据此实现的
`is_encrypted()` 判定逻辑和未加密写入通道选择逻辑。所有结论都来自实测,不同 DLP
产品/策略可能不同,库的应对方式是运行时探测而非硬编码假设。
## 一、样本事实
两个样本文件内容相同(解密后 SHA-256 均为
`968b5db1…d2947100`),大小完全相同(2,466,572 字节)。加密文件在磁盘上存密文,
前 16 字节对比:
```
明文视图: 504b0304 0a000000 0000874e e2400000 (标准 ZIP/PPTX 头 "PK\x03\x04")
密文视图: 181b031a 1510197c 0a19 0e7c 6f726c5f (流式加密, 相同明文 0x00 在不同偏移密文不同)
```
特征:同尺寸、全文流式加密、无附加文件头/尾、无静态 magic 可识别。
## 二、文件属性层面无法区分(重要结论)
对两个样本逐项对比,全部一致:
- `attrib`:都只有 `A` 属性(无 EFS 的 E 属性等差异)
- `dir /r`:均无附加数据流(ADS
- 文件大小、时间戳:一致
**结论:无法通过任何文件元数据判断加密状态,唯一可靠的判别特征是内容。**
这正是 `is_encrypted()` 采用内容对比的原因。
## 三、进程白名单模型(实测矩阵)
DLP 透明加密按「进程身份」决定是否介入读写。用「读取加密样本看到什么」实测:
| 进程 | 父进程 | 读加密样本视图 | 结论 |
|------|--------|----------------|------|
| `python.exe` | 任意(含非白名单 bash) | 明文 (PK 头) | **身份白名单**,不继承、不降级 |
| `cmd.exe``type`) | 任意 | 明文 | 白名单 |
| `powershell.exe` | 任意 | 明文 | 白名单(读透明) |
| `certutil.exe` | bash / cmd | 密文 | **非白名单, raw 视图** |
| Git Bash msys 工具(`head`/`sha256sum`/`cat`) | bash | 密文 | 非白名单 |
| `git.exe``hash-object`) | bash | 明文 | 白名单(两样本 blob hash 相同) |
关键推论:
1. **helper 桥接设计成立**:打包 EXE(非白名单)拉起 `python.exe` 子进程,python 按身份
仍是白名单,读到的就是明文——这是库读取链路的根基。
2. **`cmd /c type` 不能当密文通道**:cmd 是白名单,读到的同样是明文。社区直觉「用 cmd
读原始内容」在本 DLP 上不成立。
3. **certutil 是本机可用的 raw 视图工具**:但 DLP 拦截白名单进程直接拉起它
python → certutil 报 `WinError 786`),必须经 `cmd /c certutil …` 中转。
4. **`cmd /c` 中文路径坑**:把整条命令作为单个字符串传参会触发 cmd 的引号剥离规则,
中文文件名丢失(`ERROR_FILE_NOT_FOUND`);用列表参数(subprocess 自动逐个加引号)
则中文路径正常。
写入侧实测:
| 写入方 | 落盘结果(raw 视图校验) |
|--------|--------------------------|
| `python.exe` 直写(.bin / .pptx 后缀) | 明文(本机策略不加密 python 写入) |
| `powershell.exe`stdin 管道 → `[IO.File]` | 明文 |
| `cmd /c more > f` | **破坏二进制**0x00→CRLF、TAB→空格),不可用 |
| `cmd /c copy /b con f` | 挂起等待控制台输入,不可用 |
| `certutil -decode - f` | 不支持 stdin 输入,不可用 |
| `findstr ^ f` | raw 密文视图,但按行重新包装(0x0A→CRLF 注入),只能看头部不能还原内容 |
注意:白名单读透明 ≠ 写加密。本机 python/powershell 读透明但写入落盘明文;
「白名单进程写入会被加密」的策略在其他机器上仍可能存在——所以写入通道必须
运行时探测,不能假设。
## 四、探索过程(方法记录)
1. **多通道哈希对比**`certutil -hashfile`(非白名单视图)对两样本哈希不同
33d3… vs 968b…),python 读取两样本哈希相同(968b…)——证明 DLP 生效、
python 白名单、certutil 非白名单,一条命令同时确认三件事。
2. **头部字节对比**`head -c 64 | xxd`(msys 工具,raw 视图)看到密文头,确认
同尺寸流加密、无附加头。
3. **文件属性对比**`attrib` / `dir /r` 无差异——排除元数据检测路线。
4. **写入通道候选实验**:逐一验证 more/copy con/certutil -decode/findstr 全部
有二进制安全性或可用性问题,最终 `powershell [Console]::OpenStandardInput()`
管道方案通过全字节(含 0x00/0x1A/0xFF)哈希校验。
5. **误报纠正**`python → cmd /c type` 一度显示明文,先怀疑「白名单继承」,
后用 `bash → cmd /c type`(仍是明文)和 `bash → python`(明文)交叉验证,
确认 python/cmd 均为身份白名单而非继承。
## 五、is_encrypted() 判定逻辑
判据:文件已加密 ⟺ 白名单视图 ≠ 非白名单(raw)视图。
难点:当前进程自身是否白名单是未知的(Python/pyd 是白名单,打包 EXE 不是),
单次对比存在歧义。`dlp_io.is_encrypted(path)` 用两阶段消歧:
```
h_raw = certutil -hashfile(path) # 非白名单 raw 视图(经 cmd /c 中转)
h_native = hashlib(当前进程读 path)
if h_raw != h_native:
return True # 视图不一致: 文件已加密, 且当前进程是白名单
# 歧义分支: 文件未加密, 或文件已加密但当前进程是非白名单 EXE
head_helper = helper(白名单 python)读前 64 KiB
head_native = 当前进程读前 64 KiB
return head_helper != head_native
```
三种运行环境全部正确:
| 环境 | 加密文件 | 未加密文件 |
|------|----------|------------|
| Python / pyd(白名单) | 阶段一即判定 True | 阶段二两视图一致 → False |
| 打包 EXE(非白名单) | 阶段一相等(都看到密文)→ 阶段二 helper 明文头 ≠ 本地密文头 → True | 阶段二一致 → False |
代价:未加密文件需一次完整 certutil 哈希 + 一次本地完整读取 + helper 前 64 KiB
读取;大文件应缓存结果。库故意不引入「期望 magic 表」——内容对比与文件类型无关。
## 六、未加密写入通道选择逻辑
目标:任何环境下 `dlp_io.open(path, "w"/"a"/"x")` 落盘都是未加密文件。
```
首次写某后缀时:
1. 环境变量 DLP_IO_WRITE_CHANNEL=direct|powershell → 强制, 跳过探测
2. 直写探测: temp 目录写探针文件(同后缀, 含 0x00/0x1A/0xFF 等边界字节)
→ certutil raw 哈希 == 内容哈希 ?
- 一致 → DIRECTEXE 环境恒真; 本机 python 也真)
- certutil 不可用 → 视为无 DLP → DIRECT
3. 否则 PowerShell 中转探测: 同 payload 经 stdin 管道由 powershell 子进程
([Console]::OpenStandardInput → [IO.File]::Open) 写盘 → 再校验 raw 哈希
- 一致 → POWERSHELL
4. 都失败 → raise DlpWriteError(绝不静默写出密文)
结果按后缀缓存(DLP 写策略常按文档类型区分)。
```
中转通道要点:字节只走 stdin 内存管道,不落地任何临时文件(白名单进程写的临时
文件本身可能被加密,会造成「密文套娃」);`FileMode` 映射 `w→Create`
`a→Append``x→CreateNew``x` 模式在通道选择前先做本地存在性检查,保证
`FileExistsError` 语义与原生一致。
已知边界:探测文件写在 temp 目录,若 DLP 策略按目录区分,探针结论可能与目标
目录不符——此时用 `DLP_IO_WRITE_CHANNEL` 显式指定。
## 七、相关已知问题
- 保护区内 `git push` 必现 `fatal: not a git repository`DLP 拦截 push 进程
`.git` 的访问模式),解法见 `.kimi-code/skills/git-push-dlp/`robocopy
镜像 `.git` 到 TEMP 后从 stage 目录推送)。
- 提交样本 fixture 时注意:本机 `git.exe` 是白名单视图,commit 会把加密样本按
明文存入仓库。测试用例已按环境自适应编写(依据本机 raw 视图与本地视图是否
一致判断期望),CI(无 DLP)与 DLP 开发机都能通过。