CTF中的zip(下)

原创文章,未经许可严禁转载

Author:ex@m1ne2

Zip操作

压缩

我们可以借助Linux下的 zip 命令来创建 .zip 压缩文件

举例:把 1.txt2.txt3.txt 压缩为 temp.zip

直接压缩:

1
$ zip temp.zip 1.txt 2.txt 3.txt

命令 zip 要求把 .zip 文件名罗列在前,待压缩文件在后面

如果把 1.txt2.txt3.txt 放入文件夹 dir 中,再把整个文件夹 dir 压缩为 temp.zip,需要指定参数 -r (递归执行)

1
$ zip -r temp.zip dir

如果缺少参数 -r,那只会单独将一个空的 dir 文件夹压缩进 temp.zip

假如要向 temp.zip 中加入一个 a.jpg,添加参数 -g,就不会产生新的压缩文件:

1
$ zip -g temp.zip a.jpg

如果 1.txt 有更改,需要把 temp.zip 中的 1.txt 也更新,添加 -u 参数:

1
$ zip -u temp.zip 1.txt

删除 temp.zip 中的 3.txt,添加参数 -d

1
$ zip -d temp.zip 3.txt

压缩3.txt 文件时,设置密码:

1
$ zip temp.zip 1.txt 2.txt 3.txt -e

然后会要求输入密码、验证密码

为每个压缩文档单独添加单行注释,使用参数 -c;为整个 .zip 文件添加多行注释,使用参数 -z(两者都是交互模式)

参数罗列

1
2
3
4
5
6
7
-r	递归执行
-g 向.zip中额外添加文件
-u 更新.zip中已存在的文件
-d 删除.zip中的文件
-e 加密
-c 每个文件单行注释
-z .zip文件多行注释

中文密码

到目前为止,还有挺多的压缩软件不支持密码为非ASCII可打印字符

  • Bandizip对 .zip 的压缩不支持中文密码
  • Linux的 zip 命令输入中文密码,解密时会出错

压缩方法

注意,哪怕同为 .zip 压缩,实测Linux下的 zip 命令和Win10下的压缩软件Bandizip进行的操作是不同的

还有,WinZip的私有压缩格式zipx.jpeg 压缩率比同类软件好,能减少20 ~ 30%——这也是对压缩方法不同的一个佐证

对于如何判断一个 .zip 是否采用相同的压缩方法,我查阅了很多资料,都没有发现...

注意压缩前后的文件大小是不能作为判断依据的,理由后面讲


解压

同样借助的是Linux下的 unzip 工具

直接解压:

1
$ unzip temp.zip

将文件解压到指定的目录下,使用参数 -d

1
$ unzip -d /d/xxx temp.zip

当解压出来的文件与当前目录下的文件重名时,unzip 会出现选项:[y]es, [n]o, [A]ll, [N]one, [r]ename,可以添加参数 -n,它的效果跟选择[n]o 选项一样,不会将重名文件解压出来

不进行解压,先查看 .zip 中的文件信息,添加参数 -l

1
$ unzip -l temp.zip

参数 -v 查看文件列表,以及压缩方法压缩比率

参数罗列

1
2
3
4
-d	指定解压文件夹
-n 重名文件不进行解压
-l 查看待解压文件信息,不进行解压
-v 查看文件列表、压缩方法和压缩比率,不进行解压

【参考】https://www.hangge.com/blog/cache/detail_1666.html


读取

.zip 的体积大小、CRC-32校验码之类的信息,我们都是通过手动打开 .zip 文件去查看的,那么能否通过脚本来获取这些信息呢?

在这里我们使用Python 3的zipfile库

Python 3的zipfile库似乎是自带的

首先通过 zipfile.ZipFile() 创建ZipFile对象,该函数的API为:

1
class zipfile.ZipFile(path[, mode = r[, compression[, allowZip64]]])
  • 参数 path 是即将要打开的 .zip 文件的路径
  • mode 是打开方式,r 表示只读、w 表示创建或覆盖写入、a 表示附加写入
  • compression 只有在写 .zip 时才会使用,它指定压缩方法,由于我们只读取,因此不用管该参数
  • 当要操作的 .zip 文件大小超过2G,就需要将 allowZip64 参数设置为 True

创建ZipFile对象后,只需要掌握几个函数就可以了:

printdir()namelist()infolist()gerinfo()

  • ZipFile.printdir()

    打印压缩包中的基本信息:文件名、最近修改时间、文件体积

  • ZipFile.namelist()

    返回一个列表,包含压缩包中的所有文件名

  • ZipFile.infolist()

    返回一个对象列表,列表中的元素都是一个ZipInfo对象,并且会罗列出每个文件的一些基本信息,如 filename 文件名、file_size 文件大小...

  • getinfo(filename)

    我们用这个函数来获取单个文件的信息,它接收文件名作为参数,然后返回对应的ZipInfo对象

    ZipInfo对象可以直接通过属性的方式获得相应的文件信息,如 ZipInfo.filenameZipInfo.file_size

    注意,ZipFile.infolist() 中罗列出来的信息是不齐全的,比如还可以获取CRC校验码ZipInfo.CRC;常用的ZipInfo属性有:

    filenamedata_timecompress_typecommentcreate_versionextract_versionflag_bitsCRCcompress_sizefile_size


爆破

fcrackzip

fcrackzip是一款专门用于破解 .zip 文件密码的工具,适用于Linux

安装

我们在Linux上安装fcrackzip,只需执行命令:

1
$ sudo apt install fcrackzip
使用

可以在终端输入 fcrackzip -h 来查看用法:

下面记录几个常用的参数:

  • -c

    指定字符集,分别有 aA1!:

    a 表示小写字码[a-z]、A 表示大写字母[A-Z]、1 表示数字[0-9]、! 表示特殊字符 !:$%&/()=?{[]}+*~#: 表示包含之后的字符

    如:-c "a1:&!" 表示小写字母、数字、以及 &!

  • -u

    fcrackzip默认会将爆破过程罗列,添加该参数后只显示爆破成功的密码

  • -l

    指定密码的长度,可以通过 - 来表示范围,如 -l 3 表示3位密码、-l 3-7 表示3到7位密码

  • -b

    使用暴力破解算法

  • -D-p

    -D 指定使用字典爆破,而 -p 则指定要使用的字典,如 fcrackzip -D -p dict.txt


ARCHPR

ARCHPR(Advanced Archive Password Recovery)是一个高度优化的口令恢复工具,适用于 .zip.rar 文件

点击打开按钮,选择待爆破压缩文件,然后选择爆破方式,点击开始!

它支持4种密码爆破方式——暴力破解掩码字典明文攻击


暴力破解(Brute-Force)

作为最普通的密码攻击手段,ARCHPR允许在范围选项卡中,选择暴力破解的字符集:

其中"所有特殊符号"为:

1
!@#$%^&*()_+-=<>,./?[]{}~:;`'|"\

此外,可以自定义字符集:通过勾选用户定义□,然后点击右边的"自定义字符集"选项

如你所见,在给定的字符集中,只囊括了ASCII可打印字符,如果 .zip 的密码是中文的话,那就必须选择"自定义字符集",并且勾选转换为OEM编码;否则密码会"Not Found"

长度选项卡中,设置密码长度:

范围选项卡的右边,可以看到:

这里就得知道,ARCHPR在暴力破解密码的时候,是采用特定的顺序的:

  • 大小字母 A - Z
  • 空格
  • 小写字母 a - z
  • 数字 0 - 9
  • 特殊字符 !@#$ ...

也就是说,暴力破解密码的顺序大致为:

1
"AAA" -> "AAB" -> "AAC" -> ... -> "AAZ" -> "AA " -> "AAa" -> "AAb" -> ... -> "AAz" -> "AA0" -> "AA1" -> ... -> "AA9" -> "AA!" -> "AA@"

上图的"开始于"有2个作用:

  1. 当你知道密码长度为5,并且第一个字符为 k,你可以在"开始于"设置 kAAAA,那么ARCHPR将跳过 AAAAAkAAAA 的前一个的暴力破解,节省时间
  2. 如果破解密码的时间太长,ARCHPR会每隔5分钟自动保存密码(在自动保存选项卡中可以选择),如果某次暴力破解被迫中断,那么可以通过查看最后一次保存的密码,并设置"开始于",直接从上次爆破的地方继续开始爆破

"结束于"的作用类似


在这里,引出ARCHPR的第一个坑:

坑1:版本不支持

先去下载 hour.zip

链接:https://pan.baidu.com/s/1Az6IyTvElnzPSjBZ3UWGMA
提取码:dgkv

hour.zip 是某道CTF题中的最后一步,flag就在压缩包里面

我们尝试用ARCHPR暴力破解出 hour.zip 的密码,结果却显示:

我尝试在网上查找出现这个报错的原因,然而只能大概的说,压缩 .zip 的压缩算法是有版本的,ARCHPR不支持较高压缩版本的 .zip

后来使用fcrackzip爆破出了 hour.zip 的密码 z1P,得到里面的 flag.txt 后,我又用Bandizip将它压缩回 flag.zip,密码还是 z1P;然后我对比了 hour.zipflag.zip 的内容,发现:

左边是 hour.zip,右边是复现的 flag.zip

红色方框中的是解压所需的pkware最低版本,虽然不知道 14 03 如何转化为版本号,但两者的数值的确不同

hour.zip14 03 手动更改为 14 00 后,发现使用ARCHPR就不会出现上面的报错信息!

然而由于是强行更改的,ARCHPR甚至爆破不出这个长度只有3的弱密码!

也就是说,对于较高压缩版本的 .zipARCHPR根本毫无用处


掩码(Mask)

掩码攻击属于局部暴力破解,如果你已经知道部分的密码,那么可以直接输入它,再用 ? 来代替那些不知道的部分;随后ARCHPR就会爆破 ? 处的字符

注意掩码攻击是固定长度的

比如我知道密码的长度为7,后4位是 love,那么就可以在掩码处填入 ???love

确定掩码后,爆破方式与暴力破解没什么不同

如果密码本身就可能包含 ?,那可以在高级选项卡中,更改默认的掩码符号 ?


字典(Dictionary)

字典相比较暴力破解更"智能",因为字典中包含了大部分可能使用的密码,而不是毫无头绪的穷尽遍历

一份好的字典是必须的,字典囊括的密码数决定了字典攻击的效果

比如Github上就有许多爆破字典:https://github.com/rootphantomer/Blasting_dictionary

ARCHPR字典攻击提供了3个选项:

  • 智能变化

    "智能变化"是针对字典元素的:假如当字典攻击password 时,ARCHPR会自动将 password 进行变化:

    PASSwordpassWORD、全小写 password、全大写 PASSWORD、首字母大写 Password、首字母小写 pASSWORD、元音字母大写 pAsswOrd、元音字母小写 PaSSWoRD、间隔变化1 PaSsWoRd、间隔变化2 pAsSwOrD

    对于字典中的每个元素,都会执行上面10种变化,增大破解几率,但破解时间会增大10倍

  • 尝试所有可能的大/小写组合

    以字典元素 password 为例,ARCHPR会依次尝试每个字母的大小写组合:

    1
    password -> passworD -> passwoRd -> passwoRD -> ... -> PASSWORD

    很显然,这种情况下破解时间会大幅增加

  • 转换为OEM编码

    当字典元素不只包含拉丁字母(Latin Letters)时,这个选项才会生效

    (具体涉及ANSI代码页,暂时略过)

字典选项卡中还有一个开始行号□,它用于规定从字典的第几行开始进行字典攻击;如果字典攻击中途停止了,最后一次攻击到的行数会写入开始行号□中,以便继续


明文攻击(Plaintext)

明文攻击也是CTF中的一类比较少见但有用的题型

原理

.zip 传统加密算法本质上是伪随机数流和明文进行异或,产生这个伪随机流需要用到3个32 bits的key;找到这3个key,就能解开加密的文件

压缩软件用这3个key加密压缩包中的所有文件,当我们得到已加密压缩包中的任意一个文件,如果我们用同样的压缩方法进行无密码的压缩,得到的无密码 .zip 和有密码的 .zip 进行比较,分析两个文件的不同点,就能得到3个key

所谓明文是指我们通过某些方法得到的已经加密的 .zip 文件中的部分文件

相同的压缩方法将该明文压缩成 .zip在拥有2.zip 文件后,由于新的 .zip 和原本的 .zip同样的压缩方法放置入了至少1个相同的文件,那么就能够根据新的 .zip 爆破出压缩密码

明文需大于12 bytes

实战

我们以Bugku的杂项题目神秘的文件为例,点击下载

解压 .rar 压缩包,得到 flag.ziplogo.png;我们发现 flag.zip 是加密的,但是里面也有一个名为 logo.png 的文件

怀疑 logo.png 就是泄露文件,首先需要检查两个 logo.png 是不是完全相同

使用WinRarlogo.png 压缩为 logo.zip(无密码),浏览 logo.zip,发现原先的 logo.png 经过ZIP压缩,会得到与 flag.zip 中的 logo.png 相同的CRC校验码—— 3e62bf64

基于此,我们可以断定两个 logo.png 是相同的,那么这就考察明文攻击

打开ARCHPR,在明文选项卡中分别导入加密的 flag.zip 和明文 logo.zip开始!

成功得到压缩密码 q1w2e3r4

剩下的求flag步骤与本文无关,略过


在这里,引出ARCHPR另外两个坑:

坑2:不同压缩算法

就上面的题目而言,在压缩 logo.png 时不使用WinRar,而是使用Linux下的 zip 命令,得到 logo.zip

然后对 flag.ziplogo.zip 发动明文攻击,但ARCHPR却给出报错:

经测试和浏览网上的write-up,就这题而言,压缩 logo.png 时使用Bandizip、2345好压、7z、快压、360压缩、Linux zip 命令,都会出现上图的报错信息;只有使用WinRar压缩后才能正常进行明文攻击

缘由是不同软件对 .zip压缩算法是不同的

然而我浏览了许多关于明文攻击的文章,关于zip不同压缩算法都是模棱两可、一笔带过

"最后请教大佬得知,压缩算法的锅,emmm,有点想哭,修改压缩算法"

"可能是文件的压缩方式与加密文件不同,尝试更换压缩方式"

找了许久,没能得到一个明确的答案,不知道如何确定所谓的"正确的压缩算法";并且就上题而言,使用WinRar压缩后的 logo.png 虽然CRC校验码相同,但是体积大小却是不同的——压缩后的体积根本不能作为相同压缩算法的判别依据

或许做明文攻击题时,遇到上图的问题需要逐个尝试不同的压缩软件...☹

补充

后面在一篇文章中找到一句话:

构造明文压缩包时要选用与加密压缩包相同的压缩软件,如果他用WinRar压的,你用7z构造出的压缩包来做明文压缩包,软件是会报错的

坑3:口令未找到

当你进行长时间的明文攻击后,最终却显示:

你以为明文攻击失败了?并没有

虽然 .zip 文件的加密原理很难理解,但有一些需要记住

.zip 的加密过程中会生成3个key(加密密钥),如果我们能够得到key,那么哪怕没有密码,也能够通过明文key复现出加密压缩包中的文件

ARCHPR明文攻击3个阶段:

  1. 阶段一:减少密钥的数量

    这一步是为后面搜寻密钥做准备的

  2. 阶段二:搜索密钥

  3. 阶段三:尝试找回口令

"口令"就是加密压缩包的密码

在阶段二,就会根据明文去找到3个key;而在阶段三,会尝试找回压缩包的密码

我个人觉得阶段三是多余了,原因是它花费了大量的时间(相对阶段2),还不保证成功

既然我们知道,拥有key就能够复现压缩包中的文件,那么就完全可以在阶段二结束后,停止明文攻击,跳过耗时久的阶段三

在阶段三直接点击停止,会出现:

点击保存可以将上述的"总计口令"、"总计时间"、"平均速度"之类的统计信息保存下来,但用处不大

点击确定ARCHPR会让你保存一个 $File_Name$_decrypted.zip 的文件,这个文件就是ARCHPR根据key复现的压缩包文件

我们可以直接通过打开 $File_Name$_decrypted.zip 查看原先被加密的文件

因此,在进行明文攻击时,不妨在结束阶段二后就停止攻击,打开保存下来的 .zip 文件,就能得到flag

练习:

链接:https://pan.baidu.com/s/1f07VM3bGuPs5frMhbuFoOw
提取码:b74h


练习

练习一:格式为flag{xxx}

链接:https://pan.baidu.com/s/1Ur7CTAqdw2s00fRCWK9RJg
提取码:ulv8

练习二:格式为flag{xxx}

链接:https://pan.baidu.com/s/1CXPXydOejb8PaUXbQwgaDg
提取码:z12i

解答一

打开 zippy.zip,发现有54个以 chunk 命名的 .zip,并且附带一句提示:the text files within each zip consist of only "printable" ASCII characters

被告知"每个 .zip 只包含可打印ASCII字符"

大致浏览一下各个 chunk.zip,里面都有一个 data.txt,并且每个 data.txt 只有4字节大小,在CRC爆破的攻击范围内

由于 .zip 文件较多,写脚本将所有 .zipCRC校验码读取出来:

1
2
3
4
5
6
7
import zipfile
crc32 = []
for i in range(54):
path = 'zippy\chunk' + str(i) + '.zip'
file = zipfile.ZipFile(path, 'r')
info = file.getinfo('data.txt')
crc32.append(info.CRC)

得到结果如图:

由于每个 data.txt 的内容为4字节的可打印ASCII字符,因此继续写脚本爆破内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import zlib

ASCII = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

def solve(crc32):
for a in ASCII:
for b in ASCII:
for c in ASCII:
for d in ASCII:
if hex(zlib.crc32(bytes(a+b+c+d, encoding='utf-8')))[2:] == crc32:
return a+b+c+d

flag = ""
crc32 = ['de65c01b', 'b156ff98', ...]
for i in crc32:
flag += solve(i)
print(flag)

可打印ASCII字符有95个,所以每个CRC校验码的爆破需要大约$95^4=81450625$次,要爆破54个CRC校验码,所以耗费的时间是比较多的,我自己花费了1h03min才爆破出来

可以适当调整一下 ASCII 的顺序,让更有可能出现的大小写英文字母在前面,能相对提高速度

最终得到结果

1
UEsDBBQDAQAAAJFy1kgWujyNLwAAACMAAAAIAAAAZmxhZy50eHT/xhoeSnjMRLuArw2FXUAIWn8UQblChs4AF1dAnT4nB5hs2SkR4fTfZZRB56Bp/FBLAQI/AxQDAQAAAJFy1kgWujyNLwAAACMAAAAIAAAAAAAAAAAAIIC0gQAAAABmbGFnLnR4dFBLBQYAAAAAAQABADYAAABVAAAAAAA=

base64解码,得到乱码,然而乱码以 PK 开头

明显是 .zip 文件内容,将base64解码内容另存为 temp.zip,结果是一个未损坏的 .zip 压缩包,而且压缩包中包含一个加密的 flag.txt

检查过不是伪加密,于是采用暴力破解,使用fcrackzip爆破出压缩密码 z1P,得到flag(版本太高,ARCHPR不支持)

Mark

BUUCTF的题目 zip 也是考察CRC爆破,还考察一点 .rar 的文件格式


解答二

解压 temp.rar,得到 Desktop.zipreadme.txt

发现 Desktop.zip 中也有一个 readme.txt,尝试将外面的那个 readme.txt 压缩成 readme.zip,发现两个 readme.txtCRC-32校验码相同,确定为明文攻击

测试了几下,发现Bandizip、WinRar压缩的 readme.zip 都被ARCHPR判定为不同压缩算法;最后发现Linux下的 zip 命令压缩的 readme.zip 能够进行明文攻击

...

那就用Linux压缩的 readme.zipDesktop.zip 进行明文攻击

明文攻击阶段二就停止,在导出的 Desktop_decrypted.zip 中打开 key.txt,得到flag

我在阶段三浪费了2个多小时,结果告诉我"口令未找到",真的鸡肋...

1
2
3
4
5
总计口令: n/a
总计时间: 2h 36m 45s 616ms
平均速度(口令/秒): n/a
这个文件的口令 : 未找到
加密密钥: [ df96dc88 b432ddfd df4b9e93 ]

Zip炸弹