资讯动态

zip伪加密原理与修复实战:标志位、十六进制修改与脚本批量处理

发布时间:2026/9/24 21:19:58 来源:尧图企业网站定制
你好这个压缩包有密码——双击一个 zip 却弹出这个提示给你文件的人又信誓旦旦说没设过密码这种尴尬我碰上过不止一次。更麻烦的场景是在安全分析时邮件网关放过了这个带密码的附件等你把文件拖进 7-Zip 想看看里面是什么发现无论输什么密码都报错。拿十六进制编辑器一翻才发现这个包压根没加密只是文件的标志位被改成了我是加密的这就是圈里常说的 zip 伪加密。第一次接触这个概念是在做样本分析时一个自称发票的 zip 用密码壳糊到了终端上。后来搞清楚原理才发现识别伪加密根本不需要暴力破解十秒钟就能判断修改也只需要动两个字节。这个知识点对逆向分析、电子取证、CTF 解题甚至普通办公族处理来路不明的压缩包都有用。这篇就把从原理到实操的完整路径写清楚。1. 先说结论伪加密到底改了什么东西1.1 一个 ZIP 里的关键结构要理解伪加密得先知道 zip 文件内部长什么样。一个正常的 zip 压缩包并非一整块数据而是由三部分结构拼起来的本地文件头Local File Header签名 PK\x03\x04每个文件条目开头都有一份里面记录了文件名、压缩方式、大小、以及一组通用标志位。中央目录文件头Central Directory File Header签名 PK\x01\x02集中在压缩包末尾相当于整份 zip 的目录索引记录了所有文件条目的信息。中央目录结束标记End of Central Directory签名 PK\x05\x06标记 zip 结束并指明中央目录从哪里开始。这三块结构里和加密相关的是一个叫通用标志位General Purpose Bit Flag的 2 字节字段位置固定区块十六进制签名标志位偏移作用本地文件头50 4B 03 04PK\x03\x04签名起始 6解压时读取决定如何解这一个条目中央目录文件头50 4B 01 02PK\x01\x02签名起始 8列出文件、校验时读取中央目录结束标记50 4B 05 06PK\x05\x06—标识 zip 结束这个标志位字段有满满一页含义其中最低的 bit0数值 0x0001就是该文件已加密的标志位。工具们看到这个位为 1就进入加密处理流程向用户要密码、用密码做校验、再解密数据。1.2 标志位一翻工具就要密码关键在这里标准工具只认标志位并不先检查数据本身到底有没有被加密。所以如果一个人先把文件正常压缩然后用十六进制编辑器把 bit0 从 0 改成 1那么这个文件在工具眼里就变成加密文件了。拿实际字节举例子。正常压缩包的标志位常见值是00 00无任何特殊标志或者08 00位 3 置位表示数据描述符。伪加密最常见的改法是把低字节改成09此时十六进制显示是09 0009的二进制是0000 1001也就是 bit0加密和 bit3数据描述符同时为 101的二进制是0000 0001只有 bit0加密为 1。当你用 WinRAR、7-Zip 打开这样的文件界面会直接弹请输入密码。你不管输什么都打不开因为工具并不是在验证密码而是尝试按 ZipCrypto 解密流程处理一段其实根本没加密的数据校验字节永远对不上。1.3 真加密和伪加密的区别真正的 zip 加密常用 ZipCrypto 或 WinZip AES会先把明文压缩再对压缩后的数据流做加密处理。ZipCrypto 加密后的数据前面还会多出一段 12 字节的加密头解密工具必须用正确的密码从这 12 字节里算出校验值对不上就直接拒绝。伪加密则完全没有这些处理只改了一个标志位。所以判断方法也简单粗暴数据区是不是明文露在外面。这里要注意文件名在 zip 标准里默认是不加密的哪怕真加密的文件你用十六进制编辑器也能看到里面的文件名。所以能看到文件名不等于没加密。真正可靠的信号在数据区本身。2. 伪加密常见来源为什么世界上会有这种文件2.1 恶意样本与网关绕行伪加密在恶意样本圈里相当常见。很多邮件网关、上传检测系统会把带密码的压缩包视为无法检测的对象直接放行或者只提示管理员人工处理。攻击者根本不需要真的给样本加密只要把标志位改成 1过网关的效果就达到了。这种情况下包里的文件十有八九是真正危险的东西。你以为有密码保护所以很安全恰恰是攻击者希望你产生的错觉。对于安全分析师来说第一件事就是确认这个包到底是真加密还是伪加密伪加密反而意味着内容可以直接拆开分析。2.2 CTF 与安全试题CTF 杂项题里zip 伪加密几乎是保留项目。题目一般给你一个带密码的 zip表面上提示需要密码实际只要你识别出是伪加密、把标志位修好就能直接拿到 flag。这种题目考察的就是对 zip 结构和二进制编辑的熟悉程度解题思路也就是本文要讲的这套流程。2.3 故意设防呆锁和误操作也有普通用户故意这么干的。有人想把文件发给别人又不想让对方随手解压但自己也不会设置真正的加密就在网上找个教程改了标志位。这种做法本质上只是防君子不防小人遇到懂行的人一秒破功。还有一类是误操作一些二次封装工具、自解压程序在打包时出了 bug把标志位写错了结果生成一个看着加密、实际没加密的包。2.4 安全提示无论哪种来源遇到疑似伪加密的压缩包第一条原则都是别因为能拆开就放松警惕。拆开之前先确认来源拆开之后也别直接双击运行。伪加密只是混淆手段它能骗过工具自然也能骗过你——包里的内容该是什么威胁还是什么威胁。3. 3 分钟判断一个 zip 是真加密还是伪加密3.1 用十六进制编辑器看标志位最直观的判别方式是直接看标志位字节。用 HxD、010 Editor或者 vim 的 xxd 模式打开 zip 文件搜索十六进制50 4B 01 02中央目录文件头找到每一处命中从签名起始位置往后数 8 个字节就是标志位如果这 2 个字节的 bit0 为 1并且数据区有明文特征那就是伪加密。看一个典型例子。文件末尾附近通常能找到50 4B 01 02 1C 00 09 00这样的序列其中第 5、6 字节1C 00是版本信息接着09 00就是标志位。看到09或01基本可以断定这个条目标志位被做过手脚。但只看标志位还不够因为真正的加密文件标志位同样是 1要结合下一条一起判断。3.2 看数据区特征本地文件头后面紧跟的就是这个条目的压缩数据区。本地文件头的签名是50 4B 03 04偏移 26 处是文件名长度偏移 28 处是扩展字段长度两者加上 30 字节的固定头部就是数据区的起点。明文压缩数据有个很明显的特征Deflate 压缩流是以78 01、78 5E、78 9C、78 DA开头的。如果压缩方式是 store不压缩方法值为 0那数据区直接就是文件明文各种文件魔数一览无余。所以判断就有章可循了若数据区以78 9C等 Deflate 头开头几乎可以肯定是没加密的伪加密若压缩方式为 store且数据区能看到PK、%PDF、\x89PNG这类明文魔数同样是伪加密若数据区前面十几个字节完全是随机的、看不到任何结构特征才可能是真加密。一个细节ZipCrypto 加密后会在压缩数据前面加 12 字节加密头所以即便压缩方式是 Deflate你也看不到开头的78特征。这就是看不到明文特征和看到明文特征两种结果背后的原理。3.3 写个 Python 脚本自动嗅探手工看几个字节不难但如果要批量处理一堆 zip还是写个小脚本快。下面这个脚本遍历每个本地文件头自动检查标志位并尝试对数据区做 Deflate 解压import struct import zlib def sniff_zip(path): with open(path, rb) as f: data f.read() pos 0 hits [] sig bPK\x03\x04 while pos len(data): idx data.find(sig, pos) if idx -1: break flag struct.unpack_from(H, data, idx 6)[0] method struct.unpack_from(H, data, idx 8)[0] name_len struct.unpack_from(H, data, idx 26)[0] extra_len struct.unpack_from(H, data, idx 28)[0] data_start idx 30 name_len extra_len hint if not (flag 0x0001): hint 未加密 elif method 8: # Deflate 压缩流明文开头 0x78 if data[data_start] 0x78: hint 疑似伪加密raw deflate 头可见 else: hint 可能真加密 elif method 0: # 未压缩看明文魔数 if data[data_start:data_start 4] in (bPK\x03\x04, bPK\x05\x06): hint 疑似伪加密内嵌 zip 明文可见 elif data[data_start:data_start 2] b%P: hint 疑似伪加密PDF 魔数可见 else: hint 可能真加密 hits.append({ offset: idx, method: method, flag: flag, hint: hint, }) pos idx 4 return hits if __name__ __main__: for h in sniff_zip(sample.zip): print(foffset0x{h[offset]:06x} method{h[method]} fflag0x{h[flag]:04x} - {h[hint]})这个脚本没有做完整的 zip 解析遇到嵌套 zip 或者特殊结构时命中点可能偏移但用来快速筛查一批文件已经够用。它的核心逻辑就是刚才说的两条铁律看标志位、看数据区明文特征。4. 手动解除伪加密十六进制编辑器改两个字节4.1 定位和修改步骤如果已经确认是伪加密修改就很简单了。以 HxD 为例完整步骤如下备份原始文件建议工作副本用 .zip.bak 命名用 HxD 打开副本CtrlR 打开搜索勾选 Hex 模式先搜50 4B 01 02中央目录文件头对每一处命中从签名后第 8 个字节开始看 2 字节标志位如果标志位是09 00把它改成08 00如果是01 00改成00 00。核心原则是只把 bit0 清零尽量保留其他位的含义再搜50 4B 03 04本地文件头偏移换成签名后第 6 个字节同样的值同样改保存退出用解压工具测试。修改时的对照关系可以记成一张速查表修改前标志位含义修改后建议说明09 00bit0 加密 bit3 数据描述符08 00只清加密位保留描述符标记最稳妥01 00仅 bit0 加密00 00清掉加密位即可11 00bit0 bit4 加密 其他10 00同理只把 bit0 变成 0这里特别提醒一点很多人一上来就把09改成00结果发现有的工具解压报错。原因是09里的 bit3 表示此条目带有数据描述符如果你把 bit3 一起清掉部分解压工具会把描述符那几字节当成数据来读导致文件损坏。正确做法是先只清 bit0也就是09 - 08。如果清完之后仍然报错再尝试把 bit3 也清掉改成00。4.2 改完怎么验证验证工具选最简单的Linux/macOS 上直接unzip -t fixed.zip能正常输出OK且不要求输入密码就是成功Windows 有 7-Zip 的话右键选测试不再弹密码框即可跨平台通用方案是用 Python 自带模块python -m zipfile -t fixed.zip。如果验证时报 CRC 错误先别急着认定修改失败。回顾一下是不是把非加密位的值也改了是不是只改了中央目录没改本地文件头是不是原始文件其实带有数据描述符而你把它清掉了下面一节就讲最常见的坑。4.3 为什么有些包还要修本地文件头新手最容易犯的一个错误是只改了中央目录文件头然后发现还是弹密码。原因在于 zip 规范允许工具在解压时优先读取本地文件头里的标志位。中央目录负责列出文件真正到解压数据这一步工具会重新读文件头的标志位来判断如何处理该条目。所以稳妥的做法是两处都改改完之后建议再全盘搜一遍50 4B 03 04确认没有漏网的加密标志位。还有一种情况是文件里有多个条目比如一个 zip 里塞了三个文件那就有三个本地文件头和三个中央目录文件头。每个都要单独检查漏一个就得重新弹一次密码。批量处理时务必要全部扫描。5. 批量修复Python 脚本一键搞定5.1 脚本逻辑手工改适合单文件应急遇到几十个条目的包或者要批量处理一堆样本还是脚本靠谱。脚本的逻辑和手工操作完全一致读取整个 zip 文件字节搜索所有PK\x03\x04本地文件头检查偏移 6 处的标志位若 bit0 为 1 则清除搜索所有PK\x01\x02中央目录文件头检查偏移 8 处的标志位若 bit0 为 1 则清除默认只清 bit0bit3 数据描述符标记保留写回新文件。这里有个潜在风险点如果被处理的 zip 里面嵌套了另一个 zip那内层 zip 的文件头签名会出现在外层的数据区。暴力搜索有可能把内层文件也改了造成外层数据损坏。所以脚本里我特意把改动范围控制在文件头区域但严谨做法是不要在内嵌数据上删改。日常遇到的多是单层伪加密 zip这个脚本足够用如果怕嵌套可以把搜索命中的偏移和上下文打印出来人工确认。5.2 完整脚本import struct import sys def clear_pseudo_encryption(data: bytes, keep_descriptor: bool True) - bytes: buf bytearray(data) total len(buf) def patch(sig: bytes, flag_off: int): pos 0 while pos total: idx buf.find(sig, pos) if idx -1: break flag struct.unpack_from(H, buf, idx flag_off)[0] if flag 0x0001: new_flag flag ~0x0001 if not keep_descriptor: new_flag ~0x0008 struct.pack_into(H, buf, idx flag_off, new_flag) print(foffset 0x{idx:06x}: flag 0x{flag:04x} - 0x{new_flag:04x}) pos idx 4 patch(bPK\x03\x04, 6) # 本地文件头 patch(bPK\x01\x02, 8) # 中央目录文件头 return bytes(buf) if __name__ __main__: if len(sys.argv) 3: print(usage: python fix_zip_pseudo.py input.zip output.zip) sys.exit(1) with open(sys.argv[1], rb) as f: raw f.read() fixed clear_pseudo_encryption(raw, keep_descriptorTrue) with open(sys.argv[2], wb) as f: f.write(fixed) print(done:, sys.argv[2])5.3 使用说明和注意事项用法很简单python fix_zip_pseudo.py 疑似伪加密.zip 修复版.zip python -m zipfile -t 修复版.zip几个使用要领脚本默认保留数据描述符位如果运行后解压报意外的数据结尾之类的错误把keep_descriptorTrue改成False再跑一次这会连 bit3 一起清掉脚本只修复标志位不能解密任何真正加密过的数据。如果跑完之后解压仍报错说明这个包本来就不是伪加密而是真加密老老实实去找密码处理样本类 zip 时建议先复制到隔离环境再执行避免解压环节触发里面的恶意程序修改后的文件最好用unzip -t或 7-Zip 再验一遍确保包结构没有因为误判被弄坏。6. 常见问题与排查技巧实录6.1 现象速查表现象可能原因处理办法改了标志位还是弹密码只改了中央目录没改本地文件头或多条目漏改两处都改全量搜索确认修改后解压报 CRC 错误把 bit3 数据描述符也给清了或原始数据本身已损坏恢复备份先按09 - 08的方式只清 bit0某个工具能打开某个工具仍要密码不同工具读取的头部不同以本地文件头为准全量清理Python 的 zipfile 报 File is encrypted标志位没清干净重新跑脚本确认所有命中点都修改包能解开但里面的文件是坏的包内嵌套了 zip脚本误改了内层数据人工确认命中点或换正规 zip 解析库处理清完标志位还是解不开数据区也无明文特征这本来就不是伪加密是真加密放弃修改寻找密码或尝试解密工具6.2 一些实操心得做伪加密识别这几年我总结出几个还算实用的习惯在这里分享下。第一个习惯拿到陌生 zip先复制一份副本再用脚本或十六进制编辑器看一眼文件头前后不超过十秒。这个动作已经把我这个包怎么要密码类的求助解决掉一大半很多情况下根本不用改文件你只需要告诉对方你这个包是伪加密我用什么工具直接解开的。这一招尤其适合帮同事处理那些从网页上下载的莫名其妙的压缩包。第二个习惯判断伪加密时别只看一个信号点。标志位为 1 数据区能看到78 9C这是最稳的组合。如果只有一个条件成立我会多留个心眼。真加密的文件在特定情况下也可能在数据区偶然出现78开头的字节但那概率很低而且配套的 12 字节加密头一般会把可见结构打散。第三个习惯处理完伪加密 zip 后如果是别人的重要文件我不会直接删原包而是把修复版和原包都留着让文件主人自己确认。因为改字节的操作再小心也无法保证所有工具都按预期工作。留个备份出了问题随时能回退。最后说句题外话伪加密这种手段本质上是利用了工具信任标志位这个特性做混淆。它既能被用来骗过邮件网关也能被用来出 CTF 题甚至有人拿它做假密码来限制急性子的朋友解压。但无论动机如何技术原理就一条标志位说的是你看到的加密数据区才说的是实际情况。搞懂这一点以后看到任何加密的 zip你脑子里应该先冒出三个字——先看看。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价