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

В данном задании мы получаем изображение queen.png которое невозможно открыть так как оно «повреждено».
Открываем его с помощью HxD и находим таблицу png сигнатуры.


Как мы видим идет 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}
Удачи вам и до встречи на играх! 🙂