Доброе время суток всем странникам! Сегодня мы разберемся как восстанавливать 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}
Удачи вам и до встречи на играх! 🙂