简介围绕易赛通数据泄露防护客户端加密文件难以在未安装环境打开的问题这份技术文档面向安全研究、终端数据防护排障及需要处理敏感数据的人员提供一套文件还原思路。压缩包内含1个docx文档约29KB篇幅精炼适合快速了解客户端主进程、守护进程与Filelock.sys驱动的协同方式。文档梳理驱动层Hook关键API实现透明加解密的结构重点归纳加密触发条件进程名是否在加密列表、文件扩展名是否在对应列表中共同决定是否加密并举例说明不同扩展名的处理差异。读者可从中获得进程名与扩展名匹配规则分析、借名进程读取后再改扩展名的验证思路以及具体DEMO演示配图便于建立排查框架并评估自有防护策略。已有2086人学习下载可作为该类DLP客户端加密机制入门的参考材料。1. 拦人的不是算法一套透明加密客户端的真实边界先说结论被数据泄露防护客户端加密的文件卡住你的往往不是加密强度而是它“要不要为你服务”的那个判断。我最初也以为这类透明加密解密无解——.xlsx 拷到没装客户端的机器上直接打不开.docx 打开是一屏乱码用十六进制看文件头全是有规律的重复字节看着就像硬加密。折腾几周后我发现这套机制真正的开关是进程名和扩展名两个白名单只要你的读写动作落在它认定的可信组合里内核驱动会非常配合地把明文交出来。下面这份加密文件解密思路是我在自己被授权的那台机器和那批测试数据上真刀真枪跑通的从组件结构怎么拆、白名单怎么测、四步复现怎么走到五个我踩过的坑和最后的加固视角一条条摊开。做同类安全分析的同学可以照着复现负责这套系统落地的同学也能看到它的死穴在哪。2. 拆开客户端三个组件各管一段加解密到底藏在哪一层动手之前我先花了大半天做静态观察不是急着写代码。原因很简单如果连加解密发生在哪一层都没搞清后面所有的尝试都是瞎猜。这类客户端通常不是单一进程而是“界面 守护 驱动”的三段式结构每段的职责边界很清晰搞清楚之后你就知道哪些路根本走不通。2.1 界面进程、守护进程、驱动三段式结构各自的职责主进程模块负责 GUI、加密策略更新和用户登录鉴权我这里用虚构代称DGui.exe64 位是DGui64.exe安装在厂商目录下的客户端文件夹里。它干的是“和人打交道”的活托盘图标、策略拉取、身份认证以及把服务端下发的策略写进本地配置。真正决定“哪些进程、哪些扩展名要加密”的那份名单最终会以某种形式落到它手里。辅助进程DGuardSvc.exe64 位DGuardSvc64.exe负责守护主进程提供保护和重启能力。它的存在感平时很低但你在做测试时会强烈感受到它——你手动结束主进程几秒之内它就被拉起来了你在调试器里挂上主进程它也可能因为心跳异常而重启对方。做这类分析第一件要建立的心理预期就是不要跟守护进程硬碰硬那是纯粹浪费时间的对抗。驱动dguard_flt.sys位于System32/drivers它才是真正干活的。文件加解密在驱动层完成挂载在文件系统过滤驱动栈上对上层应用完全透明。这句话的含义是任何应用层 API 拿到的数据都已经是驱动处理过的结果。你用ReadFile读一个被加密的 .docx读回来的就是明文你用WriteFile写一个 .docx落盘时驱动会替你加密。应用层看到的世界和磁盘上的世界是两套。提示判断一个客户端是不是这套结构最简单的办法是看它的安装目录里有没有守护型进程、System32/drivers下有没有对应的.sys。有驱动就基本可以确定加解密在内核态应用层的 hook、注入、API 重写这些手段收益极低。2.2 加解密为什么在应用层完全透明Hook 在内核意味着什么很多人第一反应是“找到它的加密函数逆向出密钥”。这条路在原理上不是不行但成本极高而且极不稳定加密逻辑在驱动里密钥通常在驱动加载时协商或者由服务端下发逆向一个签过名、有反调试的内核模块投入产出比非常差。更现实的问题是就算你拿到了算法你还得处理密钥轮换、文件头版本差异、分块边界这些事一个细节对不上就是一堆打不开的残废文件。真正的关键在于理解“透明”这两个字的机制含义。驱动做的是拦截文件系统操作进程发起创建或写入请求时过滤驱动先看一眼发起方是谁、目标文件什么类型符合条件就在数据落盘前加密进程发起读取时驱动把磁盘上的密文解密后再交给调用方。整个过程调用方毫无感知它以为自己在读一个普通文件。这套设计的好处是兼容性极好任何程序都不用改坏处是它的所有判断都依赖“发起方是谁”这个信息而这个信息是可以被影响的。我在观察时反复确认了一件事驱动做的判断不是“这个文件属于谁”而是“这个进程有没有资格”。这个视角的切换是后面所有工作的前提。2.3 定位触发点的方法用最小变量法把黑匣子拆成两个维度把黑匣子拆开的标准做法是最小变量法。我准备了一批内容完全相同的测试文件扩展名各不相同然后用不同的程序去创建、读取、改名、复制每次只改一个变量记录结果。四类动作要分开测因为它们的处理路径可能不一样——创建和写入走的是加密路径读取走的是解密路径改名和复制可能只动元数据。测下来会得到一个很干净的结论这套机制关心的维度只有两个进程名和扩展名而且只有“创建”和“修改”这两个动作会触发加密。换句话说判断公式大致是进程名是否在名单扩展名是否在名单创建/修改后的落盘结果在在加密在不在不加密不在在不加密不在不在不加密这张表是整个分析的地基。它说明两件事第一加密是“与”关系两个条件同时满足才触发第二解密服务的提供也遵循同样的逻辑——只有当发起读写的进程在名单内、且扩展名在名单内时驱动才会替你把密文解成明文。所以真正要解决的问题不是“破解”而是“怎样让驱动认为这次读写应该被服务”。3. 白名单怎么测进程名 × 扩展名的双条件矩阵知道了有两个维度接下来的活就是把这俩名单尽量摸清楚。这里有个取舍要想明白完整枚举名单成本极高名单可能有几十上百项而且会随策略更新变化但你实际需要的往往只是“找到一两个能用的组合”不是拿到完整清单。所以我的策略是先广撒网找候选再做定点验证。3.1 进程名名单先确认“谁被信任”进程名名单的枚举思路是“从常见办公软件往外扩”。理由是这类客户端的产品目标就是让员工正常办公不受影响所以名单里必然包含日常使用频率最高的那批程序文本编辑器、压缩软件、Office 套件、图片查看器、PDF 阅读器、浏览器下载模块。我用一个批量脚本让每个候选程序写一个已知内容的测试文件然后检查落盘结果是否被加密。这一步的坑在于“用哪个程序写”。很多程序你没法用命令行让它安静地写一个指定内容的文件压缩软件和记事本相对好用Office 套件则必须走自动化接口。我的做法是分层能用命令行直接写的记事本、压缩工具批量跑不能的单独用自动化脚本处理剩下的靠人工点一遍。判断结果的方法也有讲究。最稳的是拿同一份内容做“加密态 vs 明文态”的对照明文样本的文件头有明显特征PK、%PDF-、UTF-8 可打印字符加密后的文件头是固定模式的高熵字节。我一般会同时看三个指标零字节密度、可打印字符占比、是否有已知魔数。三个指标都指向“不是明文”才判定为加密。3.2 扩展名名单同一个进程为什么对 .txt 和 .abc 两种态度扩展名的枚举比进程名容易因为你可以自己造文件。固定一个确定在名单里的进程比如记事本然后遍历一批扩展名.txt、.doc、.docx、.xls、.xlsx、.ppt、.pdf、.jpg、.zip、.rar、.py、.log、.abc、.temp、.bin。每写一个就立刻检查落盘状态。这里会看到一个非常干脆的分界常见的办公文档类扩展名基本都在名单里代码类、日志类、临时文件类基本都不在。这不是巧合是产品定位决定的——它要保护的是“能被带走卖钱或者泄露隐私的文档”不是程序员的.py文件。.abc这种随便编的扩展名百分之百不在名单里这也是后面“落盘时换扩展名”这个技巧的依据。注意扩展名判断通常是字符串比对大小写是否敏感、是否处理多后缀a.tar.gz、是否看文件名末尾而不是真扩展名这些细节不同产品不同版本可能不一样必须实测。我吃过一次亏以为.TXT和.txt一样处理结果大小写敏感白测了一轮。3.3 组合矩阵实测一张表把结论钉死两个维度都摸过一遍后要做的是交叉验证把矩阵填满。下面是我实际用的探测脚本骨架核心思想是“对进程 × 扩展名做笛卡尔积采样逐格判定落盘结果”。# probe_matrix.py —— 白名单矩阵探测骨架仅在被授权的测试机上运行 import os, tempfile PROCESSES [notepad.exe, wr.exe, sheet.exe] # 候选进程名按你的环境替换 EXTS [.txt, .docx, .xlsx, .pdf, .abc, .temp] # 候选扩展名 def is_encrypted(path: str) - bool: 三指标粗判零字节密度 可打印字符占比 已知魔数 with open(path, rb) as f: head f.read(512) if not head: return False if head[:2] bPK or head[:5] b%PDF-: # 明文容器特征直接否掉 return False zeros head.count(0) printable sum(1 for b in head if 32 b 127 or b in (9, 10, 13)) return zeros 8 and printable / len(head) 0.6 def make_sample(ext: str) - str: 生成内容已知的测试文件注意必须由目标进程写入才有效 d tempfile.mkdtemp(prefixprobe_) p os.path.join(d, sample ext) with open(p, w, encodingutf-8) as f: f.write(PROBE-MARKER-0123456789 * 50) # 内容固定便于对照 return p if __name__ __main__: for proc in PROCESSES: for ext in EXTS: path make_sample(ext) # 实际测试时改用目标进程写文件 flag ENCRYPTED if is_encrypted(path) else PLAIN print(f{proc:14s} {ext:8s} - {flag})is_encrypted里的两个阈值零字节数 8、可打印占比 0.6是经验值一定要用你自己环境里的明文样本做一次统计再调不同产品的密文头差异不小。make_sample那句注释是关键脚本骨架里为了跑通用了直接open写入但真正的探测必须让目标进程去写否则测的永远是当前这个 Python 进程而 Python 大概率不在名单里。矩阵填完结论就钉死了命中组合只有“进程在名单 扩展名在名单”这一种其余全部落盘为明文。到这里理论上已经没有任何未知量了剩下的只是工程实现。4. 借刀杀人四步复现让驱动自己把明文交出来这套思路之所以成立是因为它把“解密”这个动作外包给了驱动本身——我不破解算法我让驱动误以为这次读文件是合法的、应该被服务的。整个流程四步每一步都有必须注意的细节我按顺序拆开。4.1 第一步让程序以名单内的进程名运行驱动判断发起方身份最常用的依据是进程映像名。这个名字来自进程对象里那个长度受限的短名字段通常只保留文件名不含路径。也就是说如果你把自己写的读取程序编译成wr.exe对应名单里那个压缩工具的名字放在任意目录下运行驱动在这一层看到的身份就是“可信进程”。关键细节有三个。第一路径不参与判断所以不需要把程序丢进系统目录随便一个临时目录跑就行这一点很重要因为替换系统目录里的文件属于绝对不该做的事。第二名字长度有上限太长的名字会被截断比对就会失败。第三如果此时系统里已经有一个同名的进程在跑你的新进程可能被识别成已有实例导致行为不符合预期——这一点在第五节会展开。提示验证身份伪装是否生效最直接的办法是先用这个程序写一个.txt文件看落盘后是不是加密态。加密了就说明身份被认了没加密就是没被认别往下猜。4.2 第二步用这个身份把已加密文件读出来身份立住了读取这一步反而最简单用普通文件读取方式一次性把整个文件读进内存。驱动会在读路径上把密文解成明文再交给你你拿到的bytes就是原始内容。这里有两个容易翻车的地方。一是不要用内存映射memory-mapped方式读有些实现里映射路径和普通读写路径的处理分支不同映射可能拿不到解密服务你会在内存里看到一堆密文然后怀疑人生。二是尽量一次性读完而不是分块读分块本身没问题但如果客户端在块边界上做了长度对齐处理频繁跨块读可能出现尾部多出或少了几个字节的情况校验时就对不上。读完必须做一件事记下原始文件的总字节数。后面写盘时要用这个数截断否则很容易多出一截尾部。我第一次跑通时最兴奋的时刻就是看着读回来的内存头是PK\x03\x04——那一瞬间你就知道正确的.docx已经在你手上了。4.3 第三步写到一个不在名单里的扩展名上拿到明文之后不能直接写回原扩展名那等于把明文亲手送回加密器。正确做法是把它写成一个明确不在扩展名名单里的类型.temp、.bin、.dgt都可以只要你在上一章矩阵里验证过它不被处理。写入这一步同样有细节。落盘时驱动会检查发起进程和扩展名此时进程名在名单里、扩展名不在名单里两条不满足直接原样写盘明文安全落地。但我建议写入用一个独立的、不在名单里的进程来完成——比如你的主程序把bytes通过管道或者临时文件传给一个改名/落盘小工具让落盘和读取分属两个身份。这样做的好处是把风险隔开即使落盘环节被检测到异常也不会牵连读取环节。写入完成后立刻做一次长度校验新文件大小必须等于第二步记下的原始字节数。不相等就直接判定失败重来别往下走。4.4 第四步用另一个进程改扩展名并做完整性校验最后一步是把.temp改回原来的扩展名。这一步必须用一个不在白名单里的进程来做理由很直接如果改名的进程本身在白名单里某些实现会在改名动作完成后重新走一遍加密判断你刚还原的文件又被糊上一层密文。文件管理器explorer.exe就是个典型雷区它在不在名单里因产品而异别赌。# 用独立进程改名-n 表示目标已存在时不覆盖避免误伤 mv -n /data/out/report.xlsx.temp /data/out/report.xlsx # 改名完成后立刻比对哈希原始值来自加密前留存的记录 sha256sum /data/out/report.xlsx改名只是元数据操作不触发文件内容读写所以不会被重新加密。但哈希比对必须做因为改名成功不代表内容正确中间任何一步出问题都可能在哈希上暴露出来。对于 Office 系列文件光比哈希不够——如果你的原始哈希没留存就得靠结构校验来判断还原是否成功。.docx、.xlsx、.pptx本质上是 zip 容器能读出中央目录、能逐条通过 CRC 校验基本可以断定文件是完好的。import zipfile, hashlib def sha256_of(path: str) - str: h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(1 20), b): h.update(chunk) return h.hexdigest() def verify_office(path: str): docx/xlsx/pptx 的还原校验容器可读 条目 CRC 全通过 if not zipfile.is_zipfile(path): return False, not a zip container # 连 PK 头都没有说明还原失败 with zipfile.ZipFile(path) as z: bad z.testzip() # 逐条解压做 CRC 校验返回第一个坏条目 if bad: return False, fcorrupted entry: {bad} names z.namelist() required [Content_Types].xml # Office 容器必有这个条目 if required not in names: return False, missing [Content_Types].xml return True, okzipfile.is_zipfile检查的是 PK 魔数和中央目录testzip会逐条解压做 CRC 比对这两个都过了基本就是完整文件。[Content_Types].xml这个条目是 Office 容器的硬性要求缺了它文件虽然能解压但 Word 会拒绝打开——我遇到过这种情况一开始以为是解密失败查了半天发现是写入时尾部截断导致 zip 中央目录偏移错乱。5. 常见问题与排查五个翻车现场和它们的根因这套流程看起来只有四步实际跑起来我至少翻过五次车每次的表象都差不多文件打不开但根因完全不同。下面按“现象 → 原因 → 解决”逐条记你遇到问题时对号入座比从头排查快得多。5.1 现象进程名改了写出来的文件还是明文或还是密文原因通常有两个。一是同名进程冲突系统里已经有一个同名进程在跑驱动或者守护进程把你这个新实例识别成了已有对象策略快照沿用旧的。二是名字被截断或者被加了后缀比如编译产物实际叫wr (1).exe比对自然失败。解决改名前先确认没有同名实例在运行任务管理器或者tasklist扫一遍程序放在干净的临时目录避免下载副本自动加序号用完整路径确认实际文件名必要时用重命名工具改而不是复制粘贴。改完先用一个已知会加密的扩展名做一次写测试确认身份立住了再往下走。5.2 现象读出来的数据依然是乱码但文件头看着像密文原因这个文件压根不是被当前这套客户端加密的或者它处于“双重保护”状态——比如先被压缩软件加了一层密码再被客户端加密。还有一种可能是扩展名不在名单里读路径根本不提供解密服务你读到的就是原始密文。解决先做闭环验证用一个你自己用记事本写的.txt走完整流程确认工具本身没问题再检查目标文件的真实来源看它是不是从别的系统带过来的、或者被其他工具处理过。判断依据可以看密文头部的重复模式——不同产品的填充方式差别很大两段文件头模式不一致基本就能确定不是同一层加密。5.3 现象解密后文件大小对得上但 Office 打开提示损坏原因写入时没有严格按原始字节数截断尾部多了一段零填充或者分块读取时最后一块多读了几个字节。zip 容器对尾部特别敏感因为中央目录的位置是记在文件末尾的多几个字节整体偏移就全乱了。解决读取时记录精确的len(data)写入时用f.write(data)一次性写完不要用带缓冲的追加模式写完立刻用os.path.getsize比对还原后跑一遍verify_officetestzip报哪个条目坏就基本能定位到偏移问题。5.4 现象文件明明改好名了过一会儿又变成打不开的状态原因改名用的进程在白名单里改名动作触发了重新加密判断。最常见的就是用了文件管理器手动拖拽改名或者用了一个名字刚好撞上名单的第三方工具。解决改名统一用自己写的、名字明确不在名单里的小工具或者用命令行mv批量处理时把“读取落盘”和“改名”拆成两个独立进程别图省事放在一个程序里。改完名立刻算哈希并存档事后再变就能第一时间发现。5.5 现象批量跑到一半程序被结束或者文件被独占锁住读不了原因守护进程检测到异常行为模式比如短时间大量文件读写后重启了主进程顺带影响了整个会话或者目标文件正被其他程序占用读取直接失败。解决降低频率加随机延时单线程跑不要开并发处理前先把目标文件复制到本地临时目录——注意复制这个动作本身也可能被加密所以复制目标要用不在名单里的扩展名遇到被占用的文件先确认是哪个进程持有句柄能关就关关不掉就跳过记录下来别硬扛。6. 三分钟判定法一份文件到底是加密态还是原生损坏前面所有流程都建立在一个前提上你得先确认手上这份文件确实是“被客户端加密”的状态而不是本来就坏了、或者被别的工具处理过。这个判断做错后面全是无用功。我后来固化成一个三分钟就能走完的判定法顺序不能乱。第一步看魔数。用十六进制编辑器打开前 16 个字节对照已知格式.docx/.xlsx应该是50 4B 03 04.pdf是25 50 44 46 2D.jpg是FF D8 FF.png是89 50 4E 47。如果魔数对得上这文件根本没被加密打不开是内容层面的问题别再往下折腾解密流程。第二步看熵分布。取前 4KB 统计字节频率明文文档通常有明显的峰值空格、常见字母、、这类标记字符密文的分布接近均匀。我一般会算一个粗略指标可打印字符占比低于 60% 且零字节超过 8 个就高度怀疑是密文。这个阈值不是绝对的压缩过的明文比如已经是 zip 的文件也会接近均匀分布所以要和第一步结合看。第三步做对照验证。找一份你自己刚创建的、同扩展名的文件走一遍完整读取流程能正常读出来就说明工具链和身份都对问题在目标文件身上读不出来说明你的环境有问题先修环境。第四步看体积。透明加密通常会给文件加一个固定长度的头或者尾部填充同一份内容加密前后大小会有稳定差值。如果目标文件比同内容的明文大出几十到几百字节且差值在不同文件间保持一致这几乎是加密态的指纹。这套判定法帮我省下的时间比解密流程本身还多——很多时候文件打不开根本不是加密问题是传输截断、磁盘坏块或者上游系统导出的格式就有问题硬套解密流程只会把一份本来还能救的文件彻底搞坏。回头看这套机制最大的问题不在加密强度在判断依据太单薄进程名是调用方可以自己决定的扩展名是文件名的一部分两个维度都是“声明式”的而不是“证明式”的。真要加固方向很清楚——把进程身份从名字升级到签名加路径加父进程链的组合校验把扩展名判断从文件名后缀升级到内容嗅探同时对“新进程首次写非白名单扩展名”这种模式做行为审计。我在做这块验证时养成了一个习惯任何还原动作之前先留一份原始文件和哈希全程只在副本上操作。血泪经验是一次误操作把唯一一份样本写坏了后面所有的对照都没了参照只能从头再来。希望这些能帮到你少走我绕过的那些弯路。本文还有配套的精品资源点击获取