Killer Queen CTF (Shes A Killed Queen)

Доброе время суток всем странникам! Сегодня мы разберемся как восстанавливать CRC чек сумму.

В данном задании мы получаем изображение queen.png которое невозможно открыть так как оно «повреждено».

Открываем его с помощью HxD и находим таблицу png сигнатуры.

queen.png in HxD
PNG Format

Как мы видим идет 8 байт PNG Signature, 4 байта IHDR Length, 4 байта Chunk Type, 8 байт данных о высоте и длинне фотографии, 5 байт данных о фото, а так же 4 байта чек суммы.

В заметил что в задании файл не имеет размеров и 8 байт обозначают 0 на 0 пикселей. Нужно исправлять но как?

Мы имеем не тронутую чек сумму по которой все программы проверяют данный файл на повреждения. Нужно найти схему ее определения.
Порывшись чуть в интернете я наткнулся на такой сайт (https://pyokagan.name/blog/2019-10-14-png/) в начале которого объясняется как просто проверить чек сумму у png файла. Привожу часть кода с сайта которая нам сделает все дело.

#Подключаем библиотеки 
import struct
import zlib

f = open('queen.png', 'rb') 
# Открываем файл на чтение в бинарном виде

PngSignature = b'\x89PNG\r\n\x1a\n' 
if f.read(len(PngSignature)) != PngSignature: #Читаем первые байты сигнатуры
    raise Exception('Invalid PNG Signature')

def read_chunk(f):
    chunk_length, chunk_type = struct.unpack('>I4s', f.read(8)) # Читаем длину IHDR  а так же Chunk Type
    chunk_data = f.read(chunk_length) #Из длинны понимаем сколько нужно прочитать байтов дальше
    checksum = zlib.crc32(chunk_data, zlib.crc32(struct.pack('>4s', chunk_type))) #Высчитываем чек сумму по Типу и данным
    chunk_crc, = struct.unpack('>I', f.read(4)) #Захватываем уже существующую чек сумму из файла
    if chunk_crc != checksum: #Проверяем чек суммы которые мы получили между собой
        raise Exception('chunk checksum failed {} != {}'.format(chunk_crc,
            checksum)) #Выбиваем ошибку если они не похожи
    return chunk_type, chunk_data

read_chunk()

В примере выше я постарался комментариями объяснить все что можно и я думаю у вас не составит труда разобраться в этом.

Хорошо у нас есть код который высчитывает CRC сумму и проверяет ее с существующей в файле. В нашем же примере размер отсутствует а значит и чек сумма которую мы высчитаем будет не равна той что будет в файле.

В голову не приходит ничего нового как сделать перебор значений и высчитывание чек сумм пока не попадется нужная нам.

Генератор данных

Так как данные нужно генерировать в hex формате, а у меня мало опыта работы с питоном, я не придумал лучше чем данный скрипт перебора.

    max_x =8192
    max_y = 8192
    for x in range(max_x):
        #if ((x != 0) & ((int(x)/max_x) % 0.001 == 0)):
        if x!=0:print(str(x/max_x))
        for y in range(max_y):
            x_hex = str(hex(x))[2:]
            x_hex_can = ('0' * (8 - len(x_hex))) + x_hex
            y_hex = str(hex(y))[2:]
            y_hex_can = ('0' * (8 - len(y_hex))) + y_hex
            xy = x_hex_can + y_hex_can
            chunk_zize = ""
            for i in xy:
                chunk_zize += i

Страшно? Я думаю да, но ничего лучше на горячую голову в 4 часа ночи не пришло. Данный скрипт генерирует те 8 байт данных о размерах фотографии. По X и Y можно понять что начинаем от 0x0 до 8192×8192. Конечную цифру выбрали из соображения что создатели задания не сделают фото больше 8К.

Хорошо! У нас есть скрипт генерации у нас есть скрипт вычисления суммы, так чего же мы ждем? Вставляем, костылим, и запускаем!

import struct
import zlib

f = open('queen.png', 'rb')
PngSignature = b'\x89PNG\r\n\x1a\n'  #Читаем сигнатуру
if f.read(len(PngSignature)) != PngSignature: 
    raise Exception('Invalid PNG Signature')

def read_chunk(f):
    chunk_length, chunk_type = struct.unpack('>I4s', f.read(8)) #Читаем тип и Размер даты
    chunk_data = f.read(chunk_length)  # Читаем 13 байт данных
    chunk_crc, = struct.unpack('>I', f.read(4)) #Читаем CRC который в файле
    print(str(chunk_crc))
    chunk_info = chunk_data[8:14] #Вырезаем 8 байт размеров фотографии и оставляем только служебную data
    max_x = 1300
    max_y = 8192
    for x in range(max_x): # Генерируем Размеры в hex формате
        if x!=0:print(str(x/max_x))
        for y in range(max_y):
            x_hex = str(hex(x))[2:]
            x_hex_can = ('0' * (8 - len(x_hex))) + x_hex
            y_hex = str(hex(y))[2:]
            y_hex_can = ('0' * (8 - len(y_hex))) + y_hex
            xy = x_hex_can + y_hex_can
            chunk_zize = ""
            for i in xy:
                chunk_zize += i
            newchunk_data = bytes.fromhex(chunk_zize) + chunk_info #Получаем новую полную data
            checksum = zlib.crc32(newchunk_data, zlib.crc32(struct.pack('>4s', chunk_type))) #Расчет чек суммы по созданной data
            if chunk_crc == checksum: #Сравниваем нашу полученную и сумму которая существует.
                print('x:y = ' + str(x) + ':' + str(y))
                print(bytes.fromhex(chunk_zize))
                print(chunk_zize)
                raise Exception('chunk checksum failed {} = {}'.format(chunk_crc,checksum))
                break
    return chunk_type, chunk_data


chunk_type, chunk_data = read_chunk(f)

Через несколько десятков секунд скрипт попадает на нужные нам размеры и выбивает ошибку(которая нам нужна).

x:y = 1200:675
b’\x00\x00\x04\xb0\x00\x00\x02\xa3′
000004b0000002a3

Через тот же HxD меняем биты на нужные нам и вуаля!

Так как у нас вместо обычного средства просмотра Windows программа под названием Stegsolve, натыкаемся на такой слой

Пару минут в Google и мы понимаем что это шифр Марии Стюарт.

Шифр Марии Стюарт

Разобрав шифр м не понимали что за строчки мы получаем.
SHES_A_KILLED_QUEEN_BY_THE_GUILLOTINE_RANDOMCHRSIADHFKILIHASDKFHQIFPXKRL

После некоторых сомнений ключ ли это мы натыкаемся на сообщение от Администратора в Дискорде.

Ну и славно подумали мы и использовал данный флаг для решения задания!
kqctf{SHES_A_KILLED_QUEEN_BY_THE_GUILLOTINE_AS_RANDOMCHRS_IADHFKIHASDKFHQIFPXKRL}

Удачи вам и до встречи на играх! 🙂

Оставьте комментарий