一些话

因为这道题还没有结束,而且网上也没有能看的WP,所以我就不细说是哪一题了

在这篇文章,你可能会了解到:

  • 一点点追踪线索的方法
  • 分离隐藏在图片中文件
  • 查找在图片中隐藏的信息
  • 编写Python程序来解密移位密码
  • 一点点正则表达式
  • 编写Python程序来穷举SHA256
  • Hashcat的使用

可以听着朴树的一曲老歌,也可以静静地阅读

题目信息

PWNHUB公开赛 某题MISC

题目介绍

详情

获取FLAG需要耐心(英文)

文件

可于 本站网址/static/post/hard-misc-815/ext/chute.jpg 获取

正文

信息收集

给了一张图

360截图1768080388116112

了解文件名

先看看chute是什么意思

360截图17571114100614

查看文件属性

没有特别的东西

360截图16570129767295

打开图片看看

提示查看底部

360截图18720116154451

使用010Editor查看

注意到了两段信息

第一段是password,同时看到了PK字样,说明可能藏了一个zip

还看到了code.py

360截图17571115108108107

第二段是一段英文

1
The Answer to the Ultimate Question of Life, the Universe, and Everything

360截图18430630447331

搜了一下,答案是42

1、

360截图17571114272676

2、

360截图178605306186101

从图片分离文件

一般常用的有两个工具:binwalk和foremost

下面是使用方法

记得把chute.jpg放在同一目录下

如果使用binwalk

1
binwalk -e chute.jpg

360截图1729042910111995

如果使用foremost

1
foremost chute.jpg

360截图17290505327239

可以看到分离出一个zip文件

此zip需要密码

360截图168508147411284

填上上面发现的password,成功获取code.py

可以看出这是出题人加密flag的时候用的程序改造而来

1
2
3
4
5
from secret import flag
from hashlib import sha256

assert hex(int.from_bytes(sha256(bytes(flag.lower(), 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8'
assert hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'cbf28477ce64bf9e1bd268fd77ee32bc4196800e6a2ab29fa43c1cc173b14251'

获取的程序的解读

意思是从secret.py这个文件获取flag这个字符串,这道题的做法绝对不是获取secret.py

1
from secret import flag

Python中,字符串.lower()表示把字符串中的大写字母全部转换成小写字母

下面程序的意思是对flag这个字符串转换成小写,然后求sha256,转换成16进制,

再和’b9d893e313a35。。。373275e15992f8’这个sha256比较,不成立就报错。

可以看出’b9d893e313a35。。。373275e15992f8’就是全小写的flag求出来的sha256

1
assert hex(int.from_bytes(sha256(bytes(flag.lower(), 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8'

同理,下面程序对flag求sha256,转换成16进制,然后和’cbf28477。。。c1cc173b14251’这个sha256比较,不成立就报错

可以看出’cbf28477。。。c1cc173b14251’就是flag求出来的sha256

1
assert hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'cbf28477ce64bf9e1bd268fd77ee32bc4196800e6a2ab29fa43c1cc173b14251'

这个flag当然要靠我们自己去获取,因为sha256不可逆,到这里仿佛没有线索了

关于SHA256

Hash(哈希)算法的一种,属于SHA-2【安全散列算法2(Secure Hash Algorithm 2)】

类似MD5,就算输入两个差异很小的值,得出来得结果差别也很大

SHA256的十六进制结果的长度为64位,加密出来的结果是概要,一般用于确定两个文件是否相同

不像RSA,此种算法的结果不可逆,要破解SHA256目前还只能靠穷举暴力破解

上段文字是自己总结的,有错误欢迎指出

尝试穷举SHA256

因为没有找到其它线索,只好试试穷举

穷举

拿从所有字母为小写字母求出来的SHA256作为目标,排除掉26个大写字母,能节省不少时间

找pwnhub的flag格式

找到格式似乎是pwnhub{flag:xxx}

编写密码生成器

参考此处关于itertools包的用法

使用itertools包编写密码生成器

程序为python3

1
2
3
4
5
6
7
8
def gen_passwd(min_len, max_len, words):#参数:最小长度,最大长度,生成密码的参数
while True:
pwds = its.product(words, repeat=min_len)
for pwd in pwds:
yield ''.join(pwd)
min_len += 1
if min_len > max_len:
break

完整程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import itertools as its
from hashlib import sha256


words = '0123456789abcdefghijklmnopqrstuvwxyz~!#$%^&*' #生成密码的参数

can_stop = False


def gen_passwd(min_len, max_len, words):#参数:最小长度,最大长度,字符组
while True:
pwds = its.product(words, repeat=min_len)
for pwd in pwds:
yield ''.join(pwd)
min_len += 1
if min_len > max_len:
break


def cmp_sha(password):
global can_stop
count_ = 0
while not can_stop:
p = next(password)#生成一个密码

flag = "pwnhub{flag:" + p + "}"
count_ += 1#计数
if count_ % 1000000 == 0:#每跑完100万次密码提示一次
print("At :", count_, ", len =", len(p), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print(flag)
can_stop = True


if __name__ == '__main__':
password = gen_passwd(1, 8, words)
cmp_sha(password)

跑了一晚上跑了在’0123456789abcdefghijklmnopqrstuvwxyz~!#$%^&*’中生成的1位到7位

然后觉得方向可能不太对,于是停掉了

再找线索

这次仔细研究了一下图,因为前面得到的42还没有用上

斜槽,底下,42

感觉这个图有点像保险箱的那个密码旋钮,于是数了一下

上面顺时针旋转了41个。。。似乎有点线索

360截图18470125589367

让我想到了凯撒密码,一种移位密码,于是我尝试对之前获得的zip解压密码password那段字符进行位移

因为这是唯一可疑的一段字符

360截图18430704576396

移位密码

程序

程序如下,依然是python3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from hashlib import sha256

while True:
st = input("Please input\n")
if st[0:3] == "add":
st = st[3:]
len_ = int(st[:2])
st = st[2:]
for i in st:
ord_ = ord(i)
ch = ''
if ord_ + len_ > 126:
ord_ = ord_ + len_ - 126 + 32
ch = chr(ord_)

else:
ch = chr(ord_+len_)
print(ch, end="")
print()

用法

输入【add】【往后移位的数量】【字符】即可

例如运行程序后输入

1
add42\<B7=]2Q{;*5O%KH52e5<(dC517aPua03QgS517aPua03QiS52:QgS2S

表示对<B7=]2Q{;*5O%KH52e5<(dC517aPua03QgS517aPua03QiS52:QgS2S进行往后移位42的操作

得到线索

发现这个确实是线索(有坑,请继续看下去)

360截图16251112759673

得到线索前的尝试

(此部分可跳过,不阅读)

上面的线索得出来之前其实做了好几次尝试,包括如何对ascii进行位移

一开始add41

之后试add42发现有flag字样

360截图18200424859878

程序最终的126和+32都是摸索出来的

一开始是+33和126或者+33 122或者+33 126或+48 126或+48 122

360截图17150124688280

尝试1

360截图16720330495576

尝试2

360截图16720404466176

尝试3、4

感觉这时候已经差不多了

360截图18141222206667

后来找到一张图,试了126和+32才得到上面的线索

1
(flag)\{GeT_yOur_\1_fR0m_[a-zA-Z]{3}_[a-zA-Z]{5}_\d{3}\}

360截图17090922433433

再次穷举

初次看,猜想应该是pwnhub{flag:X} X为11位密码,前8位为大小写字母,后三位为数字

1
(flag)\{GeT_yOur_\1_fR0m_[a-zA-Z]{3}_[a-zA-Z]{5}_\d{3}\}

然后手动验证猜想,这时还以为离得到flag不远了

在上面程序的基础上添加验证功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from hashlib import sha256

while True:
st = input("Please input\n")
FLAG = "pwnhub{flag:" + st + "}"

if hex(int.from_bytes(sha256(bytes(FLAG.lower(), 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print("R1 Yes")
if hex(int.from_bytes(sha256(bytes(FLAG, 'utf8')).digest(), "big"))[2:] == 'cbf28477ce64bf9e1bd268fd77ee32bc4196800e6a2ab29fa43c1cc173b14251':
print("R2 Yes")
if st[0:3] == "add":
st = st[3:]
len_ = int(st[:2])
st = st[2:]
for i in st:
ord_ = ord(i)
ch = ''
if ord_ + len_ > 126:
ord_ = ord_ + len_ - 126 + 32
ch = chr(ord_)

else:
ch = chr(ord_+len_)
print(ch, end="")
print()

还是没有找到flag

360截图1814121710710191

又按猜想写了11位的密码生成器

因为穷举的是小写字母生成的sha256,所以前8位可以直接跑小写字母,后三位再跑数字

1
2
3
4
5
6
7
8
9
10
words0 = 'abcdefghijklmnopqrstuvwxyz'
words1 = '0123456789'

def gen_passwd(words0, words1):
pwds = its.product(words0, repeat=8)
pwds1 = its.product(words1, repeat=3)
for pwd in pwds:
for pwd1 in pwds1:
yield ''.join(pwd) + ''.join(pwd1)
pwds1 = its.product(words1, repeat=3)

完整程序

去掉了原来写的hex(int.from_bytes(sha256(bytes(flag.lower(), ‘utf8’)).digest(), 。。。75e15992f8’:中的flag.lower()的.lower()以提高速度(其实应该快不了多少,后来发现我坑了我自己)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import itertools as its
from hashlib import sha256

words0 = 'abcdefghijklmnopqrstuvwxyz'
words1 = '0123456789'

can_stop = False

def gen_passwd(words0, words1):
pwds = its.product(words0, repeat=8)
pwds1 = its.product(words1, repeat=3)
for pwd in pwds:
for pwd1 in pwds1:
yield ''.join(pwd) + ''.join(pwd1)
pwds1 = its.product(words1, repeat=3)

def cmp_sha(password):
global can_stop
count_ = 0
while not can_stop:
p = next(password)

flag = "pwnhub{flag:" + p + "}"
count_ += 1
if count_ % 1000000 == 0:
print("At :", count_, ", len =", len(p), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print(flag)
can_stop = True

if __name__ == '__main__':
password = gen_passwd(words0, words1)
cmp_sha(password)

跑了很久连aaaa都没跑完。。。于是停了

才发现有点不对劲:这是MISC题呀,咋整密码去了

pao2

回看线索

仔细观察

1
(flag)\{GeT_yOur_\1_fR0m_[a-zA-Z]{3}_[a-zA-Z]{5}_\d{3}\}

发现是正则表达式

{5}表示匹配5次

[a-zA-Z]{3}代表从大小写字母选三个,[a-zA-Z]{5}同理

360截图17651124222534

\d表示数字

360截图16240204515061

正则测试

使用在线正则测试

正则中的\1不是很懂,先删掉

360截图17090914246315

后来仔细看了

360截图17860608476267

测试了一下,差不多理解了

360截图17661023063928

所以flag大概长这样

360截图17090920109120126

改程序

修改手动验证的程序

注释掉了FLAG = “pwnhub{flag:” + st + “}”

改成FLAG = “flag{GeT_yOur_flag_fR0m_” + st + “}”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from hashlib import sha256

while True:
st = input("Please input\n")
#FLAG = "pwnhub{flag:" + st + "}"
FLAG = "flag{GeT_yOur_flag_fR0m_" + st + "}"
if st[0:3] == "raw":
st = st[3:]
FLAG = st
print(FLAG)
if hex(int.from_bytes(sha256(bytes(FLAG.lower(), 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print("R1 Yes")
else:
print("SHA256:", hex(int.from_bytes(sha256(bytes(FLAG.lower(), 'utf8')).digest(), "big"))[2:])
if hex(int.from_bytes(sha256(bytes(FLAG, 'utf8')).digest(), "big"))[2:] == 'cbf28477ce64bf9e1bd268fd77ee32bc4196800e6a2ab29fa43c1cc173b14251':
print("R2 Yes")
if st[0:3] == "add":
st = st[3:]
len_ = int(st[:2])
st = st[2:]
for i in st:
ord_ = ord(i)
ch = ''
if ord_ + len_ > 126:
ord_ = ord_ + len_ - 126 + 32
ch = chr(ord_)

else:
ch = chr(ord_+len_)
print(ch, end="")
print()

顺便加了原始输入功能和算sha256的功能

举例子

如果是平常,输入

1
aaa_aaaaa_001

输出

1
2
flag{GeT_yOur_flag_fR0m_aaa_aaaaa_001}
SHA256: e89df378decf2e0d1e808d378511641107b1826180134de16d4507809299069b

原始输入功能,则输入

1
rawflag{GeT_yOur_flag_fR0m_aaa_aaaaa_001}

输出

1
2
flag{GeT_yOur_flag_fR0m_aaa_aaaaa_001}
SHA256: e89df378decf2e0d1e808d378511641107b1826180134de16d4507809299069b

修改穷举的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import itertools as its
from hashlib import sha256


words0 = 'abcdefghijklmnopqrstuvwxyz'
words1 = 'abcdefghijklmnopqrstuvwxyz'
words2 = '0123456789'

can_stop = False

def gen_passwd(words0, words1, words2):
pwds = its.product(words0, repeat=3)
pwds1 = its.product(words1, repeat=5)
pwds2 = its.product(words2, repeat=3)
for pwd in pwds:
for pwd1 in pwds1:
for pwd2 in pwds2:
yield ["".join(pwd), "".join(pwd1), "".join(pwd2)]
pwds2 = its.product(words2, repeat=3)
pwds1 = its.product(words1, repeat=5)

def cmp_sha(password):
global can_stop
count_ = 0

while not can_stop:
p = next(password)

flag0 = "GeT_yOur_flag_fR0m_{}_{}_{}".format(p[0], p[1], p[2]) + "}"
flag = "flag{" + flag0
count_ += 1
if count_ % 1000000 == 0:
print("At :", count_, ", len =", len(p[0])+len(p[1])+len(p[2]), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print(flag)
can_stop = True

if __name__ == '__main__':
password = gen_passwd(words0, words1, words2)
cmp_sha(password)

继续跑

test-11cha

跑了有段时间了,我感觉方向不太对,就停掉了

test-11cha-late

再次穷举2

这次是靠

根据英语造句,“from”后面用“the”的可能性比较大

360截图18141215417437

于是把前三位换成the,这样穷举只要8位长度

又猜了下数字为042,这样穷举只要5位长度,很快

还是没猜对

test-the-1

修改了下程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import itertools as its
from hashlib import sha256

words0 = '0123456789'
words1 = 'abcdefghijklmnopqrstuvwxyz'

can_stop = False


def gen_passwd(words0, words1):
pwds = its.product(words0, repeat=3)
pwds1 = its.product(words1, repeat=5)
for pwd1 in pwds1:
for pwd in pwds:
yield ["".join(pwd1), "".join(pwd)]
pwds = its.product(words0, repeat=3)

def cmp_sha(password):
global can_stop
count_ = 0
while not can_stop:
p = next(password)

flag0 = "GeT_yOur_flag_fR0m_the_{}_{}".format(p[0],p[1]) + "}"
flag = "flag{" + flag0
count_ += 1
if count_ % 1000000 == 0:
print("At :", count_, ", len =", len(p[0])+len(p[1]), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print(flag)
can_stop = True

if __name__ == '__main__':
password = gen_passwd(words0, words1)
cmp_sha(password)

穷举完了,却没出flag,说明前三位不是“the”

the1

猜chute

test-5cha0

猜below

test-5cha01

找到错误

直觉让我先测试一下我写的程序

于是我生成了一个sha256

360截图16850815627996

填到11位长度的穷举程序里

360截图18280502233636

结果它在我眼皮底下直接过去了

360截图17900101155438

好吧,原来是少了.lower()

之前flag是pwnhub{flag:}的时候没有问题,所因为pwnhub{flag:}字母都为小写

后来换成flag{GeT_yOur_flag_fR0m}里面字母有大写,又少了.lower(),生成的sha256自然对不上

fixcode3

补上就好

再次运行,没问题

fix-find-err

又重跑了一次“the”,还是没出flag

test_the_2

再猜chute

test-5cha1

修正错误

猜过了很多单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import itertools as its
from hashlib import sha256

words0 = '0123456789'
words1 = 'abcdefghijklmnopqrstuvwxyz'

can_stop = False


def gen_passwd(words0, words1):
pwds = its.product(words0, repeat=3)
pwds1 = its.product(words1, repeat=3)
for pwd1 in pwds1:
for pwd in pwds:
yield ["".join(pwd1), "".join(pwd)]
pwds = its.product(words0, repeat=3)

def cmp_sha(password):
global can_stop
count_ = 0
while not can_stop:
p = next(password)

flag0 = "GeT_yOur_flag_fR0m_{}_guide_{}".format(p[0],p[1]) + "}"
flag = "flag{" + flag0
count_ += 1
if count_ % 1000000 == 0:
print("At :", count_, ", len =", len(p[0])+len(p[1]), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag.lower(), 'utf8')).digest(), "big"))[2:] == 'b9d893e313a35b541bf70bd2ca70e69d7579b79df3d6da7d4b373275e15992f8':
print(flag)
can_stop = True


if __name__ == '__main__':
password = gen_passwd(words0, words1)
cmp_sha(password)

尝试hashcat

因为各种尝试没有结果,我打算直接跑11位

因为python太慢(可能是我菜),听说hashcat能用GPU跑HASH,快得很

hashcat确实快,python跑6位要差不多3分钟,而hashcat不到10秒就搞定了(6秒左右)

GPU有点热

360截图17911007222120

使用方法

掩码模式

-a 3是掩码模式

hash.txt是存放sha256的文件

-m 1400代表sha256

-o 1.txt表示如果跑出来了就把结果放在1.txt

?d和?l都是掩码,?l代表一位小写字母,?d代表一位数字

1
.\hashcat.exe hash.txt -a 3 -m 1400 -o 1.txt flag{get_your_flag_fr0m_?l?l?l_?l?l?l?l?l_?d?d?d}

字典+掩码模式

-a 6是字典+掩码模式

1
.\hashcat.exe hash.txt -a 6 -m 1400 -o 1.txt dict.txt ?d?d?d

跑11位

写了个bat

因为跑出来就会有1.txt,所以直接cmd的type命令读txt就行

1
2
3
.\hashcat.exe hash.txt -a 3 -m 1400 -o 1.txt flag{get_your_flag_fr0m_?l?l?l_?l?l?l?l?l_?d?d?d}
type 1.txt
pause

时间有点恐怖,144天,于是放弃了

360截图16770806106136127

猜前三位为sha,结果不是

1
2
3
.\hashcat.exe hash.txt -a 3 -m 1400 -o 1.txt flag{get_your_flag_fr0m_sha_?l?l?l?l?l_?d?d?d}
type 1.txt
pause

360截图18231115113113110

跑5位单词

想不到其它方法了。。。于是找了一堆长度为5位的单词

可于 本站网址/static/post/hard-misc-815/ext/words.txt 获取

raw-words

写了个转换小写、读取单词、去除空行的处理程序

(python3程序)

1
2
3
4
5
6
with open("words.txt", 'r', encoding = 'utf-8') as fout:
with open("words1.txt", 'w', encoding = 'utf-8') as fout1:
for line in fout.readlines():
line = str(line)
if line[1:2] != "":
fout1.write(line[0:5].lower()+"\n")

处理完成

可于 本站网址/static/post/hard-misc-815/ext/words1.txt 获取

gen-words

又写了个按行读取单词,配合hashcat的批处理程序(因为懒得写hashcat的规则文件,而且hashcat又不支持掩码+字典+掩码模式

1
2
3
4
5
6
7
8
9
@echo off
set file=./words1.txt

for /f %%a in (%file%) do (
.\hashcat.exe hash.txt -a 3 -m 1400 -o 1.txt flag{get_your_flag_fr0m_?l?l?l_%%a_?d?d?d}
)

type 1.txt
pause

算了下时间(872*7/3600),大概2小时跑完

360截图1843070510491108

在跑了

360截图16770812231110

成功获取小写flag

竟然是troop,而且前三位竟然是jpl

flag

因为太艰苦了,有点难以置信,所以手动验证了一下

test-pass

获取大写flag

jpl有2^3=8种组合,直接写了

1
words0 = ["jpl","jpL","jPl","Jpl","jPL","JPl","JpL","JPL"]

改改程序

sha256改成大小写混合的那一串,然后只需要遍历’tropTROP’,数字直接填509

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import itertools as its
from hashlib import sha256


words0 = ["jpl","jpL","jPl","Jpl","jPL","JPl","JpL","JPL"]
words1 = 'tropTROP'


can_stop = False

def gen_passwd(words0, words1):
pwds = its.product(words1, repeat=5)
for pwd1 in words0:
for pwd in pwds:
yield ["".join(pwd1), "".join(pwd)]
pwds = its.product(words1, repeat=5)


def cmp_sha(password):
global can_stop
count_ = 0
while not can_stop:
p = next(password)

flag0 = "GeT_yOur_flag_fR0m_{}_{}_509".format(p[0],p[1]) + "}"
flag = "flag{" + flag0
count_ += 1
if count_ % 1000000 == 0:
print("At :", count_, ", len =", len(p[0])+len(p[1]), ", test:", flag)
if hex(int.from_bytes(sha256(bytes(flag, 'utf8')).digest(), "big"))[2:] == 'cbf28477ce64bf9e1bd268fd77ee32bc4196800e6a2ab29fa43c1cc173b14251':
print(flag)
can_stop = True

if __name__ == '__main__':
password = gen_passwd(words0, words1)
cmp_sha(password)

很快就出来了

getflag

验证flag

test-get-flag

最后

太难了,写wp都写了一晚上

程序共写了9个,批处理写了2个

360截图16850820113128135

EOF