跳转到内容

某某透明文档加密软件的解密方案

某某透明文档加密软件的解密方案

Section titled “某某透明文档加密软件的解密方案”

有些透明文档加密软件会让同一个文件呈现两种状态:在 Word 中可以正常打开,换一个普通读取程序,拿到的却是密文。文档内容是否可读,取决于客户端如何处理这次文件访问,以及发起访问的进程是否匹配管理员配置的规则。

这次整理的方案就从这个差异入手:让一个简单的读取程序使用策略中允许的进程名称,取得客户端返回的明文,再把明文保存到文件中。本文隐去具体产品名称,记录这类规则下已经验证的做法。

透明文档加密通常在应用与文件系统之间加入过滤逻辑。应用仍然调用普通的文件打开、读取和写入接口,客户端在这些 I/O 操作经过时检查策略,并决定是否执行加解密。

可以把它理解为在内核的文件 I/O 路径上加一层拦截逻辑。更准确的实现方式包括文件系统过滤驱动或挂钩,并不一定是直接修改内核代码、给内核“打补丁”。例如,Windows 的文件系统 minifilter 是内核态组件,可以观察、修改或阻止文件 I/O;微软也把磁盘数据的自动加解密列为典型用途。微软文件系统过滤驱动文档

下面是按进程策略返回不同内容的一种工作模型:

磁盘上的加密文档
│
▼
文件 I/O 过滤逻辑检查访问进程与策略
│
├── 匹配明文读取规则 → 客户端解密 → 应用取得明文
│
└── 不匹配 → 返回密文或拒绝访问,取决于策略

不同系统和产品版本的实现可能不同。macOS 也在逐步以系统扩展替代部分旧内核扩展,不能把所有版本都归结为同一种内核补丁。Apple 内核扩展与系统扩展说明 本次验证观察到了按应用名称读取不同内容的行为,没有逆向确认客户端内部使用的具体内核接口。

为什么一个名为 WORD 的读取程序能取得明文

Section titled “为什么一个名为 WORD 的读取程序能取得明文”

管理员后台通常会配置哪些应用参与透明加解密。假设某条规则允许名为 WORD 的进程读取 Word 文档的明文,并且客户端判断进程身份时采用的就是这个名称,那么一个名为 WORD 的自定义程序也可能匹配这条规则。

这个程序不必具备 Word 的编辑、排版或渲染功能,只需要打开文件并读取字节。客户端会在读取路径上完成解密,程序收到的已经是明文。

这里的 WORD 是示例,实际名称必须以管理员后台的配置为准。在不同平台上,配置可能使用 Microsoft Word、WINWORD.EXE 或其他名称。可执行文件名、进程名与应用显示名也不一定相同,不能只看图标下的文字。

本次环境中,使用对应应用名称启动一个 Go 读取程序,已经能够取得文档明文。这个结果只适用于已验证的规则:如果客户端还校验完整路径、签名或其他进程身份信息,仅修改文件名就未必有效。客户端本身也必须处于能够解密该文件的状态。

下面的程序只接收一个文件路径,把读取到的字节原样写到标准输出:

package main
import (
"fmt"
"io"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: reader input-file")
os.Exit(1)
}
if err := read(os.Args[1]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func read(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(os.Stdout, f)
return err
}

保存为 reader.go。假设策略匹配的进程名称确实是 WORD,编译并运行:

Terminal window
set -o pipefail
go build -o WORD reader.go
./WORD "input.docx" | cat > "output.docx"

如果配置的名称含空格,编译与执行时都需要加引号,例如:

Terminal window
go build -o "Microsoft Word" reader.go
./"Microsoft Word" "input.docx" | cat > "output.docx"

这里让读取程序输出到管道,再由普通的 cat 进程把字节保存到输出文件,相当于把客户端返回的明文“另存为”。set -o pipefail 让读取端失败时,整个管道也返回失败状态;已经创建的输出文件仍需检查。输入和输出使用不同路径,先保留原文件。不要把输出重定向到输入路径:Shell 会在读取程序启动前截断输出文件,导致原始内容丢失。

写入也受透明加密策略影响。本次实测中,同一个读取程序输出到管道时拿到的是有效 DOCX 内容,直接把标准输出重定向到 .docx 文件却会再次得到密文;改为通过普通 cat 进程保存后,输出通过了 ZIP 校验。读取程序直接写入不带文档扩展名的临时文件也通过了校验。这说明读取时取得明文与最终保存明文是两个需要分别验证的步骤。

如果目标目录或普通写入进程也触发了加密规则,上面的方式仍可能得到密文。正式工具可以由普通主进程准备临时输出文件,读取副本写出结果,再由主进程验证并决定是否替换源文件。

旧实现可以由脚本调度:按扩展名选择应用名称,生成读取程序,编译后运行,再接收结果保存。这个过程可以全部放进一个 Go 可执行文件,避免每次运行都依赖脚本解释器和编译器。

程序启动时作为普通主进程,按需把自身复制到临时目录,使用策略对应的名称启动副本。主进程创建临时输出文件,把打开的文件句柄作为副本的标准输出;副本进入内部读取模式,读取文档并写出结果。主进程等待副本结束,再检查结果和替换源文件。两种角色共用同一个编译产物,运行时无需再编译。

主程序按扩展名选择读取进程名称
→ 复制自身并启动读取副本
→ 客户端向副本提供明文
→ 副本通过标准输出写入临时文件
→ 主程序验证结果后保存

扩展名在这里用于选择策略中的应用名称,例如 .docx 对应 Word 的读取规则。它不代表文件一定加密,也不决定加密算法。

批量处理可以提供两个入口:

Terminal window
macsec-decrypt decrypt input1.docx input2.pdf
macsec-decrypt scan /path/to/directory

decrypt 处理指定文件,成功后替换原路径;scan 递归读取文件头、识别加密文件,列出路径和支持状态,用户确认后才批量处理。扫描依据应来自实际加密样本的文件格式,Finder 的锁图标只能作为界面提示。未知扩展名的加密文件仍应列出,避免把“程序不会处理”误认为“没有加密”。

从客户端读到了数据,并不能直接判定解密成功。没有匹配规则时,读取操作也可能成功,只是拿到的仍然是密文。

工具先把结果写入源文件同目录的临时文件,检查读取进程的退出状态、结果非空、已知加密头是否消失,以及内容是否与原始密文不同。对于 .docx,还可以检查 ZIP 结构与 CRC,并确认存在 word/document.xml;PDF 则需要继续检查格式或用阅读器确认内容。

Terminal window
unzip -t "output.docx"

非空、内容变化和文件头检查只是初步筛查,不能单独证明文档完整。保存后还需要用普通读取方式检查输出,避免在透明解密视图里把再次加密的文件误判为明文。

替换前也要重新检查源文件的身份、大小、修改时间和内容摘要。如果源文件在处理期间发生变化,取消替换;验证通过后,再通过同目录重命名替换原路径。这样不会因为读取失败就把原文件写成空文件,也不会用旧读取结果覆盖处理中发生的修改。

本次在 macOS 上使用真实样本副本验证了 PDF、DOCX 的明文读取与原路径替换,DOCX 的 ZIP CRC 校验通过。扫描取消、损坏密文保留、源文件在处理中变化时取消替换也已验证。其他扩展名对应的进程规则、其他加密版本和内部驱动实现没有逐一验证。

替换文件时,普通权限位和修改时间可以单独保留,但创建时间、扩展属性、ACL 和原 inode 不会自动继承。批量工具还需要报告单个文件失败并继续处理剩余项,让保存结果和失败原因都可见。