
压缩包一弹密码框就急着挂字典这大概是每个刚碰 MISC 的人都走过的弯路。BUUCTF 里那道名字就叫 zip 的题我第一次做的时候直接开 ARCHPR 跑掩码跑了十几分钟没结果回头用 010 Editor 把文件扒开一看气笑了——数据区明明白白躺在那儿压根没加密只是通用标志位里的加密位被拨成了 1。这一类东西圈里叫 zip 伪加密本质是 ZIP 格式自身的“标记位把戏”解压软件读的是标志位不是数据本身标志位说“我加密了”软件就老老实实弹密码框哪怕里面的内容是明文。搞懂这一点伪加密的题基本就是改两个字节的事而真正加密的包才轮到密码爆破、掩码、字典和明文攻击登场。这篇就把从伪加密到明文攻击这条链路完整捋一遍包括我踩过的坑和几处实测结论给还在 zip 上栽跟头的朋友做个参照。伪加密、真加密、无密码读取、CRC 反推、明文攻击这几件事看起来零散其实是同一条排查链上的不同站。我个人的习惯是永远从最省力的那一站开始先判断是不是伪加密能两字节解决的绝不动 GPU。下面按这个顺序展开每一步都给出可复现的操作和判断依据方便你直接抄。1. 别急着挂字典先分辨真加密和伪加密1.1 三条能立刻区分的观察点第一条看文件名后缀和文件类型这是最容易被忽略的。有些题给你的文件叫flag.zip但file flag.zip一跑返回的是Zip archive data之外的别的东西比如PNG image data或者RAR archive data。我遇到过改了后缀的 7z、改了后缀的图片也遇到过把 zip 当图片尾缀追加在 PNG 后面的情况。先跑一次file能省不少无用功。第二条是看压缩数据区能不能直接被 zlib 解出来。ZIP 默认压缩方法是 Deflate方法编号08 00。真加密的包数据区的开头是加密头ZipCrypto 是 12 个字节你拿 zlib 去喂它必然报错伪加密的包数据区就是一段正常的 Deflate 流理论上可以直接zlib.decompress出来。这个判断比看标志位更“硬”因为它验证的是数据本身而不是元数据。第三条也是最粗暴的一条直接把标志位清零再试解压。能正常解出来就是伪加密解出来一堆乱码或者 CRC 校验失败那才是真加密。这三条从成本低到成本高排列我一般按顺序走。1.2 为什么伪加密的包解压软件照样要密码关键在于 ZIP 把“是否加密”这个信息存了两份而且解压器读哪一份并不统一。一份在本地文件头里另一份在中央目录里。理论上两份应该一致但只要有人手工改动其中一份的对应字节就会出现“数据没加密、标志位说加密”的错位。解压软件读到加密位为 1直接跳过解密步骤弹出密码框根本不会去尝试解压数据——所以从用户视角看这包“需要密码”。这里有个反直觉的点伪加密并不是把数据打乱了再藏起来而是纯粹在元数据层面撒谎。所以修复它的成本极低低到用任何一个十六进制编辑器改一个字节就行。反过来真加密是实打实地用密码派生的密钥流把数据 XOR 过了没有正确密码就还原不出原始字节两者难度差着好几个数量级。提示判断之前先给原文件做一份备份。手工改字节最容易犯的错就是改多了或者改错了位置有了备份随时能重来不然一个几十兆的包改废了只能重新下载。2. ZIP 的字节地图三个签名块和那个决定生死的标志位2.1 本地文件头 PK\x03\x04 的字段布局ZIP 是流式格式每个文件条目在压缩包里都有两段描述一段紧贴数据前面叫本地文件头一段集中放在文件尾部叫中央目录。本地文件头的魔数是50 4B 03 04也就是 ASCII 里的PK\x03\x04。从魔数第一个字节开始算偏移各个字段的分布大致是这样偏移从 PK 起长度字段说明04签名固定PK\x03\x0442解压所需版本一般0A 00或14 0062通用标志位加密位就在这里重点盯它82压缩方法00 00存储、08 00Deflate104修改时间与日期两个 2 字节144CRC-32解压后校验用184压缩后大小224压缩前大小262文件名长度282扩展字段长度30变长文件名—变长扩展字段重点就在偏移 6 的那两个字节。小端序存储所以你在十六进制编辑器里看到的字节顺序是低位在前。比如01 00表示 bit0 置位也就是“已加密”00 00表示没加密。2.2 中央目录 PK\x01\x02 为什么还要再存一份每个条目在文件尾部还会被中央目录再描述一次魔数是50 4B 01 02。字段比本地头多因为它还记录了本地头在文件中的绝对偏移、外部属性、注释长度等。它的通用标志位在偏移 8 的位置。也就是说同样是“加密位”本地头在 6中央目录在 8差了两个字节。问题就出在这个冗余设计上。不同的解压软件读的标志位来源不一样有的以中央目录为准有的会同时校验两份还有的尤其是一些老的解压实现只看本地头。所以当有人只改了中央目录的标志位时用某些软件看这个包是“加密”的用另一些软件看却是正常的——这本身就是伪加密的一个明显信号。我第一次遇到的时候还以为是软件版本问题后来才反应过来是两份标志位不一致。2.3 常见的伪加密字节值和我实际遇到过的排列伪加密的标志位不是只有一种取值。因为通用标志位里除了 bit0加密还有 bit3数据描述符、bit11UTF-8 文件名等位。改动时如果只拨了 bit0你会看到01 00如果顺手把 bit3 也拨了那就是09 00如果再加 UTF-8就可能是09 08这种组合。我在题目里见得最多的是中央目录被改成09 00本地头保持00 00也有反过来的。所以不要死记“把 09 改成 00”这一个答案。正确的做法是定位到标志位的两个字节判断 bit0 是否为 1是就清掉。清 bit0 的算法是flag ~0x0001这个式子对01 00、09 00、09 08都成立比记死值靠谱得多。提示09 00表示 bit0 和 bit3 同时置位。bit3 是“数据描述符”标志意味着本地头里的大小字段可能是 0真实大小写在数据后面的描述符里。直接把整个字节改成00有时会顺带清掉 bit3如果原包确实是流式写入的可能导致解析异常。稳妥做法是只清 bit0保留其余位也就是09变成08。3. 手工把标志位拨回来定位、修改、验证3.1 先用命令行工具确认它“看起来”是加密的动手改之前先确认这个包确实报告为加密状态。Windows 上很多人习惯直接双击我建议先上命令行信息更全。zipinfo -v pending.zip会把每个条目的详细信息列出来包括“file security status”是 encrypted 还是 not encrypted、压缩方法、CRC、偏移量。这个输出能一次告诉你包里几个条目、哪个条目有问题。另一个更有价值的命令是7z l -slt pending.zip。它除了告诉你Encrypted 还会在Method字段里显示加密算法细节如果写的是ZipCrypto Deflate那说明用的是传统加密也就是后面明文攻击能打的那种如果写的是AES-256 Deflate那就是强加密明文攻击基本没戏。这一步判断能帮你省掉后面大量试错——我见过不少人对着 AES 加密的包折腾半天明文攻击其实方向从一开始就错了。3.2 用 010 Editor 定位标志位的两条路径010 Editor 自带 ZIP 模板ZIP.bt打开文件后直接运行模板会在左侧树形结构里把每个字段的名字和值列出来找到 General Purpose Bit Flag 那一项看 Encryption 是不是被勾上位置一目了然。这是最省心的路径缺点是模板偶尔会因为字段长度异常而解析失败。模板失灵时的第二条路径是手动搜索签名。按CtrlF把搜索模式切成十六进制搜50 4B 03 04找本地头跳到偏移 6 的那两个字节再搜50 4B 01 02找中央目录看偏移 8 的那两个字节。如果压缩包里有多个文件每个条目都会各出现一次签名需要逐个检查。改的时候直接在 010 里把字节改掉CtrlS保存即可不用另存。3.3 一段脚本把两份标志位一起清干净手工改适合只有一两个条目的包条目一多就容易漏。我后来固定用一个 Python 脚本处理逻辑很简单把全文里所有PK\x03\x04和PK\x01\x02的签名都找出来分别去清对应偏移的 bit0。脚本不依赖 zipfile 模块纯粹按字节操作所以对损坏包、拼接包也能用。import sys def clear_encrypt_bit(buf, sig, flag_offset): pos 0 count 0 while True: idx buf.find(sig, pos) if idx -1: break o idx flag_offset flag buf[o] | (buf[o 1] 8) if flag 0x0001: flag ~0x0001 # 只清 bit0保留其余位 buf[o] flag 0xFF buf[o 1] (flag 8) 0xFF count 1 pos idx 4 return count def main(src, dst): data bytearray(open(src, rb).read()) a clear_encrypt_bit(data, bPK\x03\x04, 6) # 本地文件头 b clear_encrypt_bit(data, bPK\x01\x02, 8) # 中央目录 open(dst, wb).write(data) print(flocal header fixed: {a}, central dir fixed: {b}) if __name__ __main__: main(sys.argv[1], sys.argv[2])用法就是python fix.py pending.zip fixed.zip输出会告诉你两个头各改了几处。改完先跑unzip -t fixed.zip做一次完整性测试再解压。unzip -t会把每个条目的 CRC 校验跑一遍如果日志里全是No errors detected那基本就是伪加密无疑之前的“密码”根本不存在。3.4 改完还是不行通常错在这几个地方第一种情况是只改了本地头没改中央目录或者反过来。有些软件偏偏读你没改的那一份于是依旧弹密码框。稳妥起见两个头都清别偷懒。第二种是搜索时用的是文本模式而不是十六进制模式PK\x03\x04里的\x03是非打印字符文本搜索匹配不到。第三种是把偏移算错了比如从文件绝对开头数了一堆字节才发现方向反了记住偏移都是从签名第一个字节往后算的。如果改完unzip -t报 CRC 错误那就要警惕了这个包很可能是真加密。伪加密的数据区是完整的明文流CRC 一定能对上对不上说明数据被动过手脚也就是加密本身在起作用。这时候别在伪加密上继续耗转去处理密码。提示改文件前把原始哈希记一下sha256sum pending.zip改完之后对比输出目录里的文件能确认自己动的只是标志位、没碰数据区。4. 确认是真加密后密码线索的四条来路和工具选型4.1 先榨干压缩包自身的元数据真加密了也别立刻上 GPU。ZIP 格式里能藏密码的地方比你想的多文件注释、条目注释、文件名本身、扩展字段、甚至 EOCD 后面的全局注释。命令行里unzip -z pending.zip会把全局注释打出来有些题直接把密码写在注释里。zipinfo -v的输出里也能看到条目注释和扩展字段的内容。文件名也值得看一眼。我遇到过文件名就是password_is_123456.txt的也遇到过文件名是一串十六进制、拿去解码正好是密码的情况。另外还有一个常被忽视的点如果包里只有一两个文件而且其中一个文件没加密ZIP 允许逐条目加密那个未加密的文件本身就是后面明文攻击的素材先把它抠出来存好。4.2 掩码优先纯暴力放最后密码的破解方式要按已知信息量选。如果题目给了任何暗示比如“密码是四位数字”“密码是弱口令”那就用掩码攻击把搜索空间压到可控范围。fcrackzip -b -c 1 -l 4 -u pending.zip里的-c 1表示只搜数字-l 4表示长度 4-u表示找到后解压验证一下避免假阳性。字符集参数也支持组合a1是小写字母加数字aA1再加上大写aA1!再加特殊符号。纯暴力只有在长度很短一般不超过 6 位且字符集很小的时候才划算因为搜索空间是乘数级膨胀的。我个人的顺序固定是题目提示规模 → 掩码 → 字典 → 纯暴力。字典优先用 rockyou.txt跑完没结果再考虑组合字典或者自定义规则最后才考虑纯暴力。4.3 hashcat、John、ARCHPR 三者怎么选这三样我都在用场景不太一样整理成表对照比较直观工具平台优势场景典型命令hashcat跨平台吃 GPU字典/掩码/规则大规模跑速度快hashcat -m 17200 hash.txt rockyou.txtJohn the Ripper跨平台偏 CPU快速验证、配合各种 2john 提取脚本zip2john pending.zip h.txt后john h.txtARCHPRWindows GUI图形化、支持明文攻击、新手友好界面勾选掩码与字符集即可hashcat 的-m模式号要选对选错了跑半天也不出结果。纯压缩条目的包用-m 17200存储方式不压缩的条目用-m 17210多文件的组合用-m 17220或-m 17225实在拿不准就用-m 17230校验和模式。如果是 WinZip 的 AES 加密包模式号变成-m 13600。John 这边先用zip2john把哈希提出来再交给john --formatzip h.txt跑。zip2john对多条目包会分别提取每一段哈希跑完之后用它自带的--show看结果。ARCHPR 的定位是“图形化 明文攻击”如果你在 Windows 上又不想敲命令它能同时跑掩码和字典还有内置的明文攻击功能对新手最友好。5. 明文攻击实战ZipCrypto 的弱点与 bkcrack 命令5.1 传统加密到底弱在哪ZipCrypto传统加密用的是基于 CRC 的密钥流密码经过一次哈希派生出一个 96 位内部状态这个状态被当作伪随机数的种子生成密钥流去 XOR 原始数据。这个设计有个致命弱点它只依赖密码和 CRC不依赖真正的随机数也没有加盐。只要你能拿到足够多的已知明文——12 个字节就够——就能反推出内部状态进而解开整个包。关键词是“已知明文”不是“明文密码”。具体来说你需要知道包里某个文件解压之后的前若干字节内容。12 字节是理论下限实际跑的时候多给一些会更稳成功率也更高。要注意的是AES 加密的包没有这个弱点bkcrack 和 pkcrack 都打不了方向要在一开始就用7z l -slt判断清楚。5.2 bkcrack 跑通一次明文攻击的完整流程我现在的首选是 bkcrack它比老的 pkcrack 更好用能处理已知明文不在文件开头的情况速度也快很多。假设压缩包里有个条目叫secret.txt你手里有一份它的原始内容plain.txt那基本命令就是# 基本攻击已知明文从被攻击文件的开头开始 bkcrack -C locked.zip -c secret.txt -p plain.txt # 已知明文不在开头用 -x 指定偏移和十六进制字节 bkcrack -C locked.zip -c secret.txt -x 0 48656c6c6f # 恢复出三把内部密钥之后直接解密整个包 bkcrack -C locked.zip -k 1a2b3c4d 5e6f7a8b 9c0d1e2f -D decrypted.zip # 或者生成一个用新密码保护的可解压包 bkcrack -C locked.zip -k 1a2b3c4d 5e6f7a8b 9c0d1e2f -U unlocked.zip newpass成功之后终端会打印出三组八位十六进制数那就是恢复出来的内部密钥。拿到密钥之后你有两条路用-D直接把数据解密出来或者用-U生成一个用你指定密码保护的新包方便后续用普通解压器打开。我一般两个都做-D出来的目录直接翻文件-U出来的包留着复现。5.3 明文攻击失败的六种常见原因第一种已知明文太短。虽然理论下限是 12 字节但实际跑的时候我建议至少给 24 字节以上而且内容最好有足够的熵别是一串重复字符。第二种明文对不上。如果压缩包里存的是压缩后的数据而你的plain.txt内容差一个字攻击必然失败——所以务必确认你手里的已知文件和包里那份是同一份。第三种压缩方式和级别不同这个影响的是“用同样的算法重压一遍能否对得上”bkcrack 对这块有一定容错但差异太大依然会失败。第四种加密类型判断错了包是 AES 的你却在跑 ZipCrypto 的攻击。第五种条目选错了压缩包里可能有好几个条目你拿的是 A 条目的明文去攻击 B 条目。第六种偏移没算对已知明文在文件中间然后你用默认的从头攻击。遇到失败别急着怀疑工具先把这六条挨个过一遍八成能定位到问题。提示CTF 里明文攻击的标准套路是“包里一个加密文件 题目另外给一份同源文件”。同源文件往往是同一张图的两个版本、同一份表格的两个修订内容在小范围内有差异但只要前 20 多字节一致就能打。6. 密码走不通时的旁路CRC 反推与嵌套包排查6.1 小文件加 CRC32 反推明文如果压缩包里有个很小的文件比如 3 到 6 个字节那即便密码破不出来也有戏。ZIP 会把每个条目的 CRC-32 和压缩前大小存在元数据里而这两个值是可以直接读的。对于这么短的内容暴力枚举所有可能的字节组合算一遍 CRC-32 去和元数据比对命中率极高。工具方面hashcat 有专门的 CRC32 模式-m 11500也可以自己写个 Python 脚本用zlib.crc32递归枚举。这一步的真正价值不在“还原这个小文件”本身而在于它能给你一份已知明文。小文件的内容一旦反推出来你就拿到了明文攻击的入场券可以反过来去解包里那些大文件。我在一道题里就是靠这招包里有个 4 字节的加密小文件CRC 反推出内容是flag凑够长度之后直接对同一压缩包里的另一个条目发起明文攻击整包拿下。6.2 无密码读取绕过标志位而不是绕过密码有个容易混淆的点值得说清楚所谓“无密码读取”并不是真的解开了加密而是针对伪加密包的绕过术。前面第 3 节的脚本就是干这个的——清掉 bit0 之后文件对解压器来说就是普通包直接解。对于真加密的包Python 的zipfile模块在open()时会检查flag_bits 0x1一旦命中就直接抛RuntimeError: File is encrypted, password required根本不给你读数据的机会。所以想用 Python 处理唯一干净的路径就是先把标志位改掉再用 zipfile 读。有人试过传空密码pwdb结果依然报错因为空字节串在布尔判断里是假值检查逻辑直接拦住了这条路走不通。6.3 嵌套、分卷、拼接包的排查顺序最后一类需要提的是包结构本身的异常。嵌套包很常见解压出来里面还有一层甚至三层压缩包binwalk pending.zip能一次性把内嵌的压缩包和图片列出来比手工一层层剥快得多。分卷包则是.z01、.z02加一个.zip的组合直接用 7z 解.zip那个就行别单独去解.z01。最坑的是拼接包。有人把两个 zip 文件用cat a.zip b.zip c.zip拼在一起此时前面的 zip 的中央目录和 EOCD 会被后面的内容覆盖普通解压器可能只认最后一段也可能直接报错“文件损坏”。处理方式是搜出文件里所有的PK\x05\x06EOCD 签名从最后一个往前逐个尝试截断找到能正常打开的那一段。这类题我吃过亏一开始以为文件被改坏了其实是两道题的内容摞在了一起。我在这类题上的固定排查顺序已经比较稳定了file确认类型binwalk看嵌套zipinfo -v看条目和加密状态7z l -slt区分 ZipCrypto 还是 AES然后才决定是改标志位还是走密码这条路。密码这条路上永远是掩码先于字典、字典先于暴力明文攻击永远优先于暴力——因为前者是几秒钟的事后者可能是几个小时的事。把顺序排对大部分 zip 题都能在十分钟内理出头绪。最后分享一个我自己写进备忘录的小习惯拿到手先复制一份重命名为pending_bak.zip所有实验都在副本上做原始的哈希值抄在笔记里。zip 这道题本身不难难的是很多人一上来就默认“要密码 需要破解”在错误的方向上耗掉了大把时间。先把标志位那两个字节看清楚再谈密码顺序对了题就简单了。