docs: DLP 环境特征调研记录与判定/写入逻辑说明
- docs/DLP_ENVIRONMENT_NOTES.md: 白名单矩阵/密文样本/属性不可区分等实测结论与探索过程 - README: DLP 程序化特征与 is_encrypted/写入通道实现逻辑摘要
This commit is contained in:
@@ -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 哈希 == 内容哈希 ?
|
||||
- 一致 → DIRECT(EXE 环境恒真; 本机 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 开发机都能通过。
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
当前版本为 `dlp-io==0.2.0`,distribution 名是 `dlp-io`,import 名是 `dlp_io`。
|
||||
|
||||
DLP 环境的实测特征(白名单矩阵、密文样本字节对比、各写入候选通道的可用性实验)与判定/写入逻辑的完整推导记录见 [DLP_ENVIRONMENT_NOTES.md](DLP_ENVIRONMENT_NOTES.md)。
|
||||
|
||||
## 安装
|
||||
|
||||
### 方式一:Gitea PyPI registry(推荐)
|
||||
|
||||
Reference in New Issue
Block a user