# MISC

这次的 MISC 是真的佩服 Aura 佬的题目,给跪了,orz
81-BAFF6-BBBD09424-BEAC9-CEE5258-AEE8.jpg

# 派森

题目:

腐乳昂木 奥普瑞特儿 阴坡尔特 艾克斯奥尔
腐乳昂木 提克有第爱慕 阴坡尔特 ⭐
弗拉格 等于 布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉布拉
印刻 等于 左中括号右中括号
佛儿 唉 因 梯软者左括号 零,楞左括号弗拉格右括号,四右括号冒号
    印刻。鹅潘德(艾克斯奥尔(奥尔德(弗拉格【唉】),奥尔德(弗拉格【唉加二】)))
    印刻。鹅潘德(艾克斯奥尔(奥尔德(弗拉格【唉加一】),奥尔德(弗拉格【唉加三】)))
    印刻。鹅潘德(奥尔德(弗拉格【唉加二】))
    印刻。鹅潘德(奥尔德(弗拉格【唉加三】))
普瑞因恩特左括号印刻右括号
井号[16, 29, 67, 84, 31, 75, 89, 48, 30, 111, 107, 48, 49, 52, 95, 67, 6, 2, 110, 51, 44, 69, 95, 118, 74, 45, 121, 95, 70, 84, 49, 49, 0, 0, 33, 33, 0, 10, 113, 125]

用大 G 老师读取一下(可能有点问题):
7-A76-A7025880-F1-E51705-BD8549-A154-C8.png
4732-D9-BEC2-BCD864-E56420-B79-DC3943-C.png

# 模型的秘密

给了字典,直接字典爆破压缩包即可
QQ20241104-164447.png

里面的文件是 model.blend , 这里还特意下了一个 Blender 来打开,不过文件头有问题,直接拖到 Blender 无法打开,需要修改文件头,这里不知道 blend文件 的文件头可以随便保存一个 blend 文件查看即可。
QQ20241104-165246.png
QQ20241104-165545.png
可以看到文件头要修改为 42 4C 45 4E 44 45 52 , 后面即可用 Blender 正常打开
QQ20241104-165853.png

# Picture

给的文件是 1 个代码 + 2 个图片。两个图片就是代码运行的结果,这就是个逆推 Crypto 题,丢给大 G 老师写一个逆推脚本即可

from PIL import Image

def reconstruct_flag(flag1_path, flag2_path, output_path):
    image1 = Image.open(flag1_path).convert('RGB')
    image2 = Image.open(flag2_path).convert('RGB')

    if image1.size != image2.size:
        raise ValueError("flag1.png和flag2.png的尺寸不一致。")

    width, height = image1.size
    reconstructed = Image.new('RGB', (width, height))

    for i in range(width):
        for j in range(height):
            r1, g1, b1 = image1.getpixel((i, j))
            r2, g2, b2 = image2.getpixel((i, j))
            r = (r1 + r2) % 256
            g = (g1 + g2) % 256
            b = (b1 + b2) % 256
            reconstructed.putpixel((i, j), (r, g, b))

    reconstructed.save(output_path)
    print(f"成功还原图像并保存为 {output_path}")

# 使用示例
reconstruct_flag('flag1.png', 'flag2.png', 'restored_flag.png')

得到的图片是一个空白图片,第一反应就是 Stegsolve.jar 打开查看各通道
QQ20241104-171046.png

# 外星信号 Ultra

这一题真是被 Aura 折服了,套了好几层,尤其是第一个不同设备查看图片是真的抽象...

压缩包里面给了 3 个东西,给的图片有代码,丢给大 G 老师说是将 wav 文件生成 data,让大 G 写了逆推代码,不过少了关键 seed ,跑不出来,从图片本身下手了。
QQ20241106-090046.png
这个 Apple Devices may see this picture differently 是真的抽象,直接搜索发现几个文章可以看出来是个什么事。

事件介绍

生成此类图片方法

此类图片介绍 1

此类图片介绍 2

还原原图片思路参考

这里看了几个文章了解了一下是怎么回事,后来参考 Ambiguous PNG Packer 作者 Issues 里面给的另一个项目 parallel-png-proposal,让博士(GPT o1-preview)写了一个还原脚本:

import zlib
from PIL import Image

def read_png_chunk(f):
    length_bytes = f.read(4)
    if len(length_bytes) < 4:
        return None, None
    length = int.from_bytes(length_bytes, 'big')
    chunk_type = f.read(4)
    chunk_data = f.read(length)
    crc = f.read(4)
    return chunk_type, chunk_data

def decompress_headerless(data):
    d = zlib.decompressobj(wbits=-15)
    result = d.decompress(data)
    result += d.flush()
    return result

def extract_pixel_data(decompressed_data, width, height):
    pixels = []
    pos = 0
    stride = 1 + width * 3
    for y in range(height):
        if pos >= len(decompressed_data):
            break
        filter_type = decompressed_data[pos]
        assert filter_type == 0, "Unsupported filter type"
        pos +=1
        row_data = decompressed_data[pos:pos + width * 3]
        pixels.append(row_data)
        pos += width *3
    return b''.join(pixels)

def main():
    with open('output.png', 'rb') as f:
        png_magic = f.read(8)
        assert png_magic == b'\x89PNG\r\n\x1a\n'
        # Initialize variables
        width = height = None
        a = b''
        b_data = b''
        idat_chunk_count = 0
        while True:
            chunk_type, chunk_data = read_png_chunk(f)
            if chunk_type is None:
                break
            if chunk_type == b'IHDR':
                width = int.from_bytes(chunk_data[0:4], 'big')
                height = int.from_bytes(chunk_data[4:8], 'big')
                bit_depth = chunk_data[8]
                color_type = chunk_data[9]
                compression_method = chunk_data[10]
                filter_method = chunk_data[11]
                interlace_method = chunk_data[12]
            elif chunk_type == b'IDAT':
                idat_chunk_count +=1
                if idat_chunk_count == 1:
                    a = chunk_data
                elif idat_chunk_count >= 2:
                    b_data += chunk_data
            elif chunk_type == b'IEND':
                break
            else:
                pass
        # Check that we have width and height
        assert width is not None and height is not None

    # For image 1 (world_output.png)
    decompressed_a = decompress_headerless(a[2:])  # 跳过 zlib 头部的 2 个字节
    decompressed_b = decompress_headerless(b_data[:-4])  # 去除末尾的 adler32 校验和
    image1_data = decompressed_a + decompressed_b
    image1_pixels = extract_pixel_data(image1_data, width, height)
    image1 = Image.frombytes('RGB', (width, height), image1_pixels)
    image1.save('world_output.png')
    print("Saved world_output.png")

    # For image 2 (apple_output.png)
    data_ab = a + b_data
    image2_data = zlib.decompress(data_ab)
    image2_pixels = extract_pixel_data(image2_data, width, height)
    image2 = Image.frombytes('RGB', (width, height), image2_pixels)
    image2.save('apple_output.png')
    print("Saved apple_output.png")

if __name__ == "__main__":
    main()

这里放出两张图片的对比:
apple-output.png
world-output.png
可以发现多了一个 random seed(496534891) ,这也是逆推回去的关键。

import random
import os
import sys

def decrypt_file(enc_file_path, output_file_path, seed=496534891):
    """
    通过对加密文件执行XOR操作,还原原始文件。

    :param enc_file_path: 加密后的输入文件路径(例如 'data')
    :param output_file_path: 输出的还原文件路径(例如 'deepsea.wav')
    :param seed: 用于生成随机数的种子(默认为496534891)
    """
    # 检查输入文件是否存在
    if not os.path.isfile(enc_file_path):
        print(f"错误:加密文件 '{enc_file_path}' 不存在。")
        sys.exit(1)
    
    # 设置随机种子
    random.seed(seed)
    
    try:
        # 读取加密文件的字节数据
        with open(enc_file_path, 'rb') as f:
            enc_data = f.read()
        
        # 生成与加密时相同的随机数序列,并执行XOR操作
        dec_data = bytearray()
        for byte in enc_data:
            rand_num = random.randint(10, 20)
            dec_byte = byte ^ rand_num
            dec_data.append(dec_byte)
        
        # 将解密后的数据写入输出文件
        with open(output_file_path, 'wb') as f:
            f.write(dec_data)
        
        print(f"成功:已还原文件 '{output_file_path}'。")
    
    except Exception as e:
        print(f"发生错误:{e}")
        sys.exit(1)

def main():
    """
    主函数,处理命令行参数并调用解密函数。
    """
    import argparse

    parser = argparse.ArgumentParser(description="还原被加密的deepsea.wav文件。")
    parser.add_argument('-i', '--input', type=str, default='data', help="加密后的输入文件路径(默认: 'data')")
    parser.add_argument('-o', '--output', type=str, default='deepsea_restored.wav', help="输出的还原文件路径(默认: 'deepsea_restored.wav')")
    parser.add_argument('-s', '--seed', type=int, default=496534891, help="用于生成随机数的种子(默认: 496534891)")
    
    args = parser.parse_args()
    
    decrypt_file(args.input, args.output, args.seed)

if __name__ == "__main__":
    main()

还原成功的 deepsea_restored.wav 是可以正常播放的一段音乐,到这里才解决第一步吧... 还原后的 wav 里还藏着东西,先拖到 DeepSound 里面分离一下,发现还有一段音频
QQ20241106-092112.png
分离出来的 sese.wav 以为是摩斯,哪知道解不出来,后来想到题目是外星信号,就回忆了之前 BaseCTF 的一题是无线电解密,搜了相关文章 ——SSTV 音频转图片,跟着步骤来解出了密码
1.png
得到的密码 9982443531668 ,解开压缩包得到一个 base(pow(2,11)) ,这里也是上次 0xGame 的题目, pow(2,11)=2048 ,也就是 base2048 解码(base2048 解码网站)。
QQ20241106-093026.png