中国石油第三届网络安全攻防大赛|Spiral Writeup

偶然间看到群友赛后发的题目,觉得有一些题目比较新颖,所以正好做一下玩玩🤪

Spiral

打开附件看到有flag.7z和spiral.png两个文件,猜测是需要从spiral.png图片中提取key,然后再去解密flag.7z压缩包。

首先,打开spiral.png看看:

分析后发现这是一张尺寸为1024*1024的正方形图片,根据题目名称和图片内容,它含有非常明显的螺旋特征。再查看图片的hex数据:

发现图片中存在提示:

ZmxhZ3tmYWtlX2ZsYWd9IEJ1dCBSR0JB6Imy5b2p56m66Ze057y65LiA5LiN5Y+v5ZOmfiDmiJHmlZnkvaDov5jljp86YUhSMGNITTZMeTlpYkc5bkxtTnpaRzR1Ym1WMEwwZFhYM2RuTDJGeWRHbGpiR1V2WkdWMFlXbHNjeTh4TWpBME1EWXhPVEk9C

base64解密后可以看到提示:

flag{fake_flag} But RGBA色彩空间缺一不可哦~ 我教你还原:aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0dXX3dnL2FydGljbGUvZGV0YWlscy8xMjA0MDYxOTI=

再次base64解密可以看到螺旋矩阵的算法实现https://blog.csdn.net/GW_wg/article/details/120406192

def function(n):
    matrix = [[0] * n for _ in range(n)]

    number = 1
    left, right, up, down = 0, n - 1, 0, n - 1
    while left < right and up < down:
        # 从左到右
        for i in range(left, right):
            matrix[up][i] = number
            number += 1

        # 从上到下
        for i in range(up, down):
            matrix[i][right] = number
            number += 1

        # 从右向左
        for i in range(right, left, -1):
            matrix[down][i] = number
            number += 1

        for i in range(down, up, -1):
            matrix[i][left] = number
            number += 1
        left += 1
        right -= 1
        up += 1
        down -= 1
    # n 为奇数的时候,正方形中间会有个单独的空格需要单独填充
    if n % 2 != 0: 
        matrix[n // 2][n // 2] = number
    return matrix

根据以上信息,大胆猜测spiral.png是按照从左到右、从上到下的顺序读取一张正常的图片像素之后,再按照螺旋的方法写入新的矩阵里面所生成的。
写入螺旋矩阵顺序:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

    所以我们可以根据这个猜想先按照螺旋的方法读取spiral.png所有像素,然后再按行还原这个图片。
from PIL import Image
import numpy as np

def reverse_spiral(image_path):

    image = Image.open(image_path)
    pixels = np.array(image)
    n = image.width
    result = np.zeros_like(pixels)

    matrix = [[0] * n for _ in range(n)]

    number = 1
    left, right, up, down = 0, n - 1, 0, n - 1
    while left < right and up < down:
        # 从左到右
        for i in range(left, right):
            matrix[up][i] = number
            number += 1

        # 从上到下
        for i in range(up, down):
            matrix[i][right] = number
            number += 1

        # 从右向左
        for i in range(right, left, -1):
            matrix[down][i] = number
            number += 1

        # 从下到上
        for i in range(down, up, -1):
            matrix[i][left] = number
            number += 1

        left += 1
        right -= 1
        up += 1
        down -= 1

    # n 为奇数的时候,正方形中间会有个单独的空格需要单独填充
    if n % 2 != 0:
        matrix[n // 2][n // 2] = number

    '''
        获取当前位置在螺旋遍历中对应的索引index
        (0,0)->1
        (0,1)->2
        (0,2)->3
        (0,3)->4
        (0,4)->5
        (0,5)->6
        (0,6)->7
        (0,7)->8
        (0,8)->9
        (0,9)->10
    '''
    for i in range(n):
        for j in range(n):
            # print('(' + str(i) + ',' + str(j) + ')', end = '->')
            print(matrix[i][j])
            index = matrix[i][j] - 1
            x = index // n
            y = index % n
            result[x][y] = pixels[i][j]

    # 图片二值化
    result[result != 255] = 0

    result_image = Image.fromarray(result)
    return result_image

image_path = "./spiral.png"
restored_image = reverse_spiral(image_path)
restored_image.save("flag.png")
restored_image.show()

跑完脚本之后成功还原这张图片,但是发现只有PASSWORD字样,解密flag.7z无果。

观察这张图片下方发现有大量留白,使用stegSolve分析可以看到flag.7z的压缩包密码:

但是这样不是很优雅,根据题目提示RGBA色彩空间缺一不可,读取所有像素后发现有类似于254的灰度像素值,因此大胆猜测还有一些灰度像素点肉眼无法分辨,可以暴力一些直接把图片二值化,像素不为255的全部变成0,也就是黑色。

# 图片二值化
result[result != 255] = 0


成功拿到flag.7z压缩包密码mUv8vvGRMNK5mgbxPNsH
打开压缩包后是一个flag字符画,调整窗口大小即可拿到flag: