0x00 前言
挺不错的一道题,思路值得学习,所以简单记录下。
0x01 恢复文件
查看文件头为PK,改后缀名为zip,解压得到三个文件,分别是
- flag.enc:加密后的flag
- encryption:加密算法脚本
- 一段base64:解码后转二进制文件,查看文件头JFIF,为图片(DivergenceMeter.jpg)
0x02 读加密算法逻辑
算法逻辑主要可分为两部分:
1.对t0的求解。
其中t0的值和操作系统平台(Windows/Linux/Darwin),字长位数(32/64),当前用户uid,随机小数r(范围0-2,且最多6位小数),m的md5值有关。
t0 | OS | 字长位数 | uid | r | 32位md5(m) |
---|---|---|---|---|---|
m | 字长位数 | ‘bit’ | uid | r1 | |
r | 个位(0或1) | 小数位(最多6位) | |||
r1 | 个位(0或1) | 小数位(最多2位) |
这里还可以知道t0的位数应该在48左右(5+2+2+7+32)
2.对t1的求解及使用
t1的值由flag的值和t0异或得到(位数也在48左右),之后算法使用了给的图片文件(DivergenceMeter.jpg),在文件中找和t1各个字符(t1[i])相同的值img[k],并提取索引值k,转换为字符(k与255取模保证在ascii码范围内),遍历t1生成表l,l的结构大致如下:
t1[i] | 索引 | 列表(data) |
---|---|---|
t1[0] | 0 | chr(k1),chr(k2),….,chr(kn)(其中k1,..kn为图片中和t1[0]相同的字符所在位置) |
… | … | … |
t1[n] | n | chr(k1),chr(k2),….,chr(kn)(其中k1,..kn为图片中和t1[n]相同的字符所在位置) |
得到表l后,纵向遍历该表,并把读取数据写入result,最终得到加密后的flag文件flag.enc。
0x03 恢复t1
由上述逻辑可知,突破点在flag.enc,假设t1取48位,则flag.enc的前48位应该是t1[i] (i=0,1,…,47)的第一个元素,即chr(k1),因此可以通过遍历图片文件,将t1的值扩大到包含所有字符(256个)的类表l的索引表table:
ascii码(索引) | 列表(data) |
---|---|
0 | chr(k1),chr(k2),….,chr(kn)(其中k1,..kn为图片中和chr(0)相同的字符所在位置) |
… | … |
255 | chr(k1),chr(k2),….,chr(kn)(其中k1,..kn为图片中和chr(255)相同的字符所在位置) |
得到表table后,遍历该表的table[i][0]查找和flag.enc的前48位(记为head)分别相同的索引值转ascii并记录,得到t1。
这里有个问题,存在多个table[i][0]和head[j]相同的情况,因此还要判断table[i][1]的情况。打印出几个值判断下:
由于t1位数限制关系,因此table[i][1]应该和table[i][0]距离不远,在文件中查找,基本可以确定A9是t1构成的表l[0][1],因此确定了i,从这里也可以得到t1的实际位数应该是45位,之后的写个代码取flag.enc的46位开始和table[i][1]判断下就可以,具体看最后的代码实现部分。
0x04 恢复t0
上一步中得到了t1,因此这一步主要是通过以下几方面确定t0的值:
t0 | OS | 字长位数 | uid | r | 32位md5(m) |
---|---|---|---|---|---|
m | 字长位数 | ‘bit’ | uid | r1 | |
r | 个位(0或1) | 小数位(最多6位) | |||
r1 | 个位(0或1) | 小数位(最多2位) |
1.确定操作系统及字长位数
根据题目提示,flag.txt中含完整flag,也就是说前几位为’flag{‘,将已知的flag与t1异或得到t0的头几位为’Darwi’,基本确定操作系统为’Darwin’,又因为python文档中写明若为该类型时机器为64位,故得到t0的前8位为’Darwin64’。
2.确定32位md5值
根据题目提示,flag的最后一位应该是’}’,因此md5的最后一位与t1的最后一位异或得到的值应该是’}’,运算得知为’e’,即得到的md5最后一位应该是’e’。
m的前几位已知为’64bit’,开始爆破uid和r1,r1由已知应该是1到3位,uid的位数可以通过t0确定(由于t0为45位,r最少为1位,可以算出uid位数应该在1-4之间[1,45-8-32-1])
写脚本爆破并对求得的符合要求的md5值(末位为’e’)与t1的末32位异或,找到符合要求的部分flag(只含字母、数字、’_’、’}’)
得到部分flag:’ce_0f_st3ints_G4t3_1p048596_233}’
对应md5值为afe46f1497072540f60d793e1e5a2a6e,uid为520,r1为125
3.确定r值
由上述uid的值可以确定r的位数应该为2(45-8-32-3),注意到r的第一位不是0就是1,写脚本爆破并组合成最终的t0和t1进行异或,打印出符合要求的flag(只含字母、数字、’_’、’{‘,’}’)。
这里有个问题,uid为520时打印出的flag为flag{to7^im91ce_0f_st3ints_G4t3_1p048596_233},改了uid为420才输出成功。
最终t0:Darwin6442000afe46f1497072540f60d793e1e5a2a6e
最终flag:flag{to7_im91ce_0f_st3ints_G4t3_1p048596_233}
0x05 解密脚本