Повторим Python на практике, вспомним бинарные сдвиги, функции chr, ord и bin, работу с файлами и списковые включения. Да, и в итоге найдем флаг.
Автор статьи никого не призывает к правонарушениям и отказывается нести ответственность за ваши действия. Вся информация предоставлена исключительно в ознакомительных целях. Все действия происходят на виртуальных машинах и внутри локальной сети автора. Спасибо!
Вчера решив задачу из раздела «Форензика» немного расширил кругозор по метаданным EXIF. Сегодняшняя задача с сайта picoCTF.org из раздела «Реверс-инжинирнг» оказалась не такой простой.
Дан файл enc и задание: I wonder what this really is... enc ''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)]). Открыв файл и увидев иероглифы, я задал себе тот же вопрос: «Что это на самом деле?». На всякий случай попробовал перевести через гугл переводчик. Да, это наивно.
Немного поразмыслив и прочитав еще раз данное условие, предположил, что строка кода и есть дешифратор (тогда в чем смысл задания?). Взглянем на нее еще раз:
''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])
Если вы проходите курсы на сайте Codebra, то вы с легкостью заметили здесь списковое включение. Отвлекся немного. Давайте напишем рабочий код:
with open("enc", "r", encoding='utf-8') as f:
flag = f.read()
print([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag) - 1, 2)])
Немного пришлось модифицировать range() после получения исключения IndexError. Не будем заострять на этом внимание.
Они действительно больше 0x110000 (или 1114112 в десятичной системе). Здесь всего девять чисел и, следовательно, девять «возможных» символов (как раз хватит только на picoCTF{}, что должно было заставить задуматься). Меня это не остановило, и я продолжил движение не в ту сторону: брал модуль 0x110000 от этих чисел, чтобы попасть в диапазон и совершал другие бесполезные вещи.
Потеряв на все эти манипуляции около часа, все же перечитал задание и догадался: при помощи этого кода была получена строка из иероглифов. Ну да, задача в разделе «Реверс-инжиниринг» как бы предполагает от результата вернуться к исходному значению.
Взглянем на код и полученный результат и для начала определим количество исходных символов. В файле их 19. Если проанализировать код, то можно предположить о 38 исходных символах. Из двух символов получается один путем смещения битов первого и сложения со следующим.
Этот конкретный ответ, который нужно реализовать в коде, поставил меня в тупик. Достаточно большое количество двух чисел дают одну и ту же сумму. Ответ кроется в бинарном сдвиге. Давайте разбираться. Начнем с простых примеров:
a = 3 # 011
print(a << 1) # 110
Число три в двоичной системе представляется как 011 (не будем указывать лишние ведущие нули). Надеюсь вы помните, как переводить из двоичной системы в десятичную. Во второй строке показан бинарный сдвиг влево на 1 регистр. Замечательно, вернемся к задаче. Для начала переведем иероглифы в числовые значения из таблицы символов Unicode при помощи функции ord() и сохраним в списке code.
Теперь будем рассуждать, как получить из числа 28777 два числа (x и y), которые в сумме дают его, при этом первое было сдвинуто на 8 регистров.
28777 = (x << 8) + y
Проще это будет понять через код:
x = 28777
i = x
print(x, bin(x)) # 28777 0b111000001101001
x = x >> 8
print(x, bin(x)) # 112 0b1110000
y = x << 8
print(y, bin(y)) # 28672 0b111000000000000
y = i - y
print(y, bin(y)) # 105 0b000000001101001
Мы взяли число 28777 и сохранили в двух переменных. Для начала получим первое число сдвигом вправо на 8 регистров. Второе неизвестное число должно быть равно в двоичной системе 0b1101001, чтобы в сумме получить 28777. Поэтому сдвигаем полученное значение обратно на 8 регистров. Далее вычитаем из 0b111000001101001 число 0b111000000000000 и получаем 0b000000001101001 (добавил ведущие нули, чтобы нагляднее было). Первый иероглиф был получен из двух символов, которым соответствуют коды 112 и 105.
Теперь реализуем это в коде:
with open("enc", "r", encoding='utf-8') as f:
flag = f.read()
code = [28777, 25455, 17236, 18043, 12598, 24418, 26996, 29535, 26990, 29556, 13108, 25695, 28518, 24376, 24370, 13878, 14388, 25394, 12413]
i = 0
for item in range(0, len(flag)):
print(chr((code[i] >> 8)), chr(abs(((code[i] >> 8) << 8) - code[i])), end='', sep='')
i += 1
Таким нехитрым способом был найден флаг. Эта задача действительно заставила напрячь мозги и вспомнить функции chr, ord и bin, работу бинарного сдвига и ограничения chr().