HTB M0rsarchive

题目描述:Just unzip the archive ... several times ...
解题参考:https://blog.csdn.net/wanmiqi/article/details/115873643

题目给出如下文件:

image-20230603230204872

本题的解题思路其实挺简单的。每一层都有一个pwd.png的图片和flag_xxx.zip的压缩包,pwd.png是该层压缩包的解压密码,使用摩斯密码图的形式进行表示。我们要做的就是识别图片中给出的压缩包密码,然后递归解压压缩包即可拿到flag。

难点在于如何去识别图中的摩斯密码,以及如何去递归解压。

首先我们可能会想到通过AI算法去识别图形,然后拿到图片的中的摩斯密码。该方法可以实现,但是太过麻烦。另一种方法是直接读取像素块,然后拿到摩斯密码。具体的话可以这样做:

(1)右击查看图片pwd.png的属性,可以看到,这是一张25×3像素的png图片。

image-20230603231254328

(2)为了更加直观的理解像素(块)的概念,我们使用 Photoshop 打开该图片,并放大进行查看。

image-20230603231728257

可以看到,这张图片一共有3行,每一行包含25个方格子,也就是前面看到的图片像素数25×3

(3)通过数上面图片中的格子数,我们可以看到有13个红色的格子和(25*3-13)个粉红色格子。粉红色格子在这里的作用就是背景色。我们可以通过Python的PIL库,去读取该图片的所有的像素块(格子),然后剔除掉粉色的格子就可以拿到正确表示摩斯密码的格子了。接着再将莫斯密码格子转换为摩斯密码字符串,继续通过查表的方式识别出该摩斯密码表示的明文内容,这样我们就拿到了压缩包的正确解压密码了。

(4)从图片中读取摩斯密码的函数可以这样实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_morse():
    fp = open('./pwd.png', 'rb')  #读取png图片
    image = Image.open(fp)        #使用PIL模块打开该图片文件句柄
    pixel = list(image.getdata()) #获取该图片所有的像素块
    background = pixel[0]         #读取背景色像素块(给出的图片中第一个像素块都是背景色)
    chars = []                    #用于存放摩斯密码字符串的变量
    for i,v in enumerate(pixel):  #遍历所有像素块,区分背景色块和非背景色块(摩斯密码有效信息)
        if v == background:       #背景色块用空格字符表示,非背景色块用"*"字符表示
                chars.append(" ")
        else:
                chars.append("*")
    output =  "".join(chars)      #提取出的所有信息(包括背景色块的信息和非背景色块的信息)
    
    #对结果进行正则匹配,提取出非背景色块的数据(摩斯密码正文)
    output = re.sub(r'^\s*', '', output)   #匹配开头的任意个空白字符,并替换为空
    output = re.sub(r'\s*$', '', output)   #匹配结尾的任意个空白字符,并替换为空
    output = re.sub(r'\*{3}', '-', output) #匹配3个*号,并替换为字符"-"
    output = re.sub(r'\*', '.', output)    #匹配单个*号,并替换为字符"."
    output = re.sub(r'\s{2,}', ' | ', output)  #(用于处理多行摩斯密码的情况)匹配两个以上空白字符,如果存在,就替换为"|"
    output = re.sub(r'\s', '', output)     #匹配空白字符,并替换为空
    output = output.split('|')             #多个摩斯密码字符直接用"|"分隔
    fp.close()    #关闭该图片文件的资源占用
    return output

该函数执行完成后,可以提取到图片中的摩斯密码字符串。

正则匹配测试,建议使用该网站:https://regex101.com/

(5)有了摩斯密码后,我们还需要查找摩斯密码表找到对应的明文。代码可以这样实现:

1
2
3
4
5
6
7
#根据摩斯密码表查值
def get_pass(morse_list):
    password = ""
    MORSE_CODE_DICT = {'.-': 'a', '-...': 'b', '-.-.': 'c', '-..': 'd','.': 'e', '..-.': 'f', '--.': 'g', '....': 'h','..': 'i', '.---': 'j', '-.-': 'k', '.-..': 'l','--': 'm', '-.': 'n', '---': 'o', '.--.': 'p','--.-': 'q', '.-.': 'r', '...': 's', '-': 't','..-': 'u', '...-': 'v', '.--': 'w', '-..-': 'x','-.--': 'y', '--..': 'z', '-----': '0', '.----': '1','..---': '2', '...--': '3', '....-': '4', '.....': '5','-....': '6', '--...': '7', '---..': '8', '----.': '9','-..-.': '/', '.-.-.-': '.', '-.--.-': ')', '..--..': '?','-.--.': '(', '-....-': '-', '--..--': ','}
    for morse in morse_list:
        password += MORSE_CODE_DICT.get(morse)
    return password

该函数执行完成后,可以将传入的摩斯密码字符串转换为明文字符串。

(6)最后再循环遍历读取压缩包解压密码、解压压缩包、进入子目录继续读取压缩包解压密码、解压压缩包即可。

这个题目的脚本编写过程还是蛮有意思的,能学到不少东西,比如如何用Python读取像素块,正则匹配,文件路径处理,解压压缩包等。

需要注意:处理压缩包时要使用相对路径,绝对路径太长了,会导致脚本报错。

参考学习的文章中,后续代码实现并不是特别方便,比如需要用bash脚本去调用该python脚本实现循环解压压缩包、需要用bash脚本去单独读取flag文件。我们可以把代码进行一下优化。

这不是纯属给自己找事干吗?对,但这样做之后,你会更加熟练Python的很多操作,说不定哪天就用上了。我们平时刷题的目的是提高自己的综合能力,而不是拿到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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import re
import os
import sys
import zipfile
from PIL import Image

#根据摩斯密码表查值
def get_pass(morse_list):
    password = ""
    MORSE_CODE_DICT = {'.-': 'a', '-...': 'b', '-.-.': 'c', '-..': 'd','.': 'e', '..-.': 'f', '--.': 'g', '....': 'h','..': 'i', '.---': 'j', '-.-': 'k', '.-..': 'l','--': 'm', '-.': 'n', '---': 'o', '.--.': 'p','--.-': 'q', '.-.': 'r', '...': 's', '-': 't','..-': 'u', '...-': 'v', '.--': 'w', '-..-': 'x','-.--': 'y', '--..': 'z', '-----': '0', '.----': '1','..---': '2', '...--': '3', '....-': '4', '.....': '5','-....': '6', '--...': '7', '---..': '8', '----.': '9','-..-.': '/', '.-.-.-': '.', '-.--.-': ')', '..--..': '?','-.--.': '(', '-....-': '-', '--..--': ','}
    for morse in morse_list:
        password += MORSE_CODE_DICT.get(morse)
    return password


#从图片中读取摩斯密码
def get_morse():
    fp = open('./pwd.png', 'rb')
    image = Image.open(fp)
    pixel = list(image.getdata())
    background = pixel[0]
    chars = []
    for i,v in enumerate(pixel):
        if v == background:
                chars.append(" ")
        else:
                chars.append("*")
    output =  "".join(chars)
    """正则匹配测试建议:https://regex101.com/
    ^  : asserts position at start of a line
    $  : asserts position at the end of a line
    \s : matches any whitespace character (equivalent to [\r\n\t\f\v ])
    *  : matches the previous token between zero and unlimited times, as many times as possible, giving back as needed (greedy)
    \* : matches the character *
    {3}: matches the previous token exactly 3 times
    """
    output = re.sub(r'^\s*', '', output)   #匹配开头的任意个空白字符,并替换为空
    output = re.sub(r'\s*$', '', output)   #匹配结尾的任意个空白字符,并替换为空
    output = re.sub(r'\*{3}', '-', output) #匹配3个*号,并替换为字符"-"
    output = re.sub(r'\*', '.', output)    #匹配单个*号,并替换为字符"."
    output = re.sub(r'\s{2,}', ' | ', output)  #(用于处理多行摩斯密码的情况)匹配两个以上空白字符,如果存在,就替换为"|"
    output = re.sub(r'\s', '', output)     #匹配空白字符,并替换为空
    output = output.split('|')
    fp.close()
    return output


#指定密码解压压缩包
def unzip_file(path, number, password):
    zip_path = "flag_" + str(1000-number) + ".zip"
    fp = zipfile.ZipFile(zip_path)
    for file in fp.namelist():
        fp.extract(file,"./",pwd=password.encode("utf-8"))
    fp.close()


def main():
    path = sys.path[0]            #当前脚本的运行目录
    
    for number in range(1,1001):
        print("Processing the "+ str(number) + "th archive.")
        #print(os.listdir('.'))   #显示当前目录下的所有文件
        morse_list = get_morse()
        password = get_pass(morse_list)
        unzip_file(path, number, password)
        path = "./flag"
        os.chdir(path)       #切换当前工作目录(进入flag子目录)
    
    fp = open('./flag', 'r')
    flag = fp.readlines()
    print(flag)
    fp.close()


if __name__ == "__main__":
    main()

接下来,我们只需要执行下这个脚本就可以了。

image-20230604000705233

image-20230604000723393

执行完拿到的flag:

image-20230604001250741

可以试着自己想下,如果不用上面的脚本,如何去读取1000文件夹里面的flag文件呢?

image-20230604001503328

最后。举一反三,以后再遇到需要循环遍历解压压缩包的题目,直接掏出这个脚本来改改就能用了。

updatedupdated2023-12-052023-12-05