Writeup GRSU CTF Junior.Crypt.2023
PaulСтеганография
Easy flag
Для решения этой задачи нужно увеличить число отображаемых пикселей. Воспользуемся утилитой 010 editor.
Смотрим на википедии структуру файла jpg, находим то что нужно.
Увеличиваем значение Y_image примерно на 300 пикселей, сохраняем.
Открываем картинку и видим ту часть изображения, которая раньше не отображалась.
grodno{0n3_m1nut3_t45k}
Зверь внутри
DOCX - формат для хранения электронных документов Microsoft Office. Формат представляет собой zip-архив, содержащий текст в виде XML, графику и другие данные. Откроем файл как zip-архив.
Поверхностно просмотрев все xml-файлы замечаем в document.xml подозрительный комментарий.
По формату сразу понимаем, что это кодировка base-64, раскодируем и получаем флаг.
grodno{d0cx_1s_an_arch1v3}
Шум
Открываем аудиофайл в любом текстовом или hex-редакторе. Сразу замечаем "PNG", то есть внутри зашита картинка. Гуглим сигнатуру png: 89504e47odoa1a0a
Всё что находится перед сигнатурой png удаляем. Меняем расширение файла на png, запускаем.
grodno{3to_pro100_k4rt1nka}
Стего в офисе. II
Читая условие, обращаем внимание на второй элемент, который образует книги – «расстояние между буквами». Инструмент для поиска находим во вкладке «Шрифт» - «Дополнительно» - «Межзнаковый интервал» - «Масштаб». Используя форму «Найти и заменить» легко находим, что в тексте присутствуют интервалы с масштабом 100% (нормальный текст), а также 101%, 102%, 103% и 104%. Заменяем латинскими буквами A, B, C, D (они не встречаются в тексте).
Весь остальной текст убираем. Получили такую строку:
ADADADCAACCABCDBDACDABACACCADDCAACDADAADAAACAAADCCBAABACBAADABDBCDADBBDDAADDAADDDADCDACADACCDADDBADDAAAADAACBAAAADDDCADDDBADDBADCDDBDBADDDACCCDABDCABBDCCDADDAAADCDAADACAABCCCCABCDABCCCAAAAABBDDDBCABCDAACABDCDAACDADCABDDADBDAACADDADBCDAAABADAAACCAAACCBCBDCDACDBDADBADDCAADCADCAADABAABAACADDCDADCAAACDCDABBDCBAAAACDBBDAAACADBADDDBADBAAABADAACAAAADDCADDCBABAABDDCAACACADBBACCDCDADBBDCACDBADCDAABBADACABDDCBDACABCBCDCDAACDAAAAACACBDBDCDBCBADBCDDADDAADBADADDDAADACBADDBAADADAADAAAAAADDACDCCDDAACCDDBADBDDCDADDDADCABAAADACAAAABADDDADACABADBDADAAABADDAABCDABBDABADCBAAAAAADCDAABADDADDADDDCDADDBADBDBAAADCDADBDCDDACDDBBCBCDAADACCAABDAABAAABABDDADDBCBBBCBDDBADDAADCCBDCBACCAADACAAAABADBBDCDADBAABADDADAACBDADDCAADCCCCDDADDACCBCBACBDABDBCCBACDCDDABABDACABDDDAACDDAACCBDACBACAABAAADADCDBCDAAACBDADCDDAACABDACCDBBBBDDAAADAAABAACABCBDDCCAADDCDADBDBDCCBAADBABADBBDADBBBDDDADBCDBAADACBCBABADCDCABDDCCBDABBABDDCBADCDDDBABDDDDCADBCBDCBABBBDBBABACDDDAADDADCCBDDBDADDBDADCBBBDDADACDDDDDADBAACCDAADBADACDDDDDACDCDBACDADDACBDDADDCDAABCDDADBDDDABBBDDCDBABDBDDACDADCDDDBDABDDDADBAABADDACCCDAAAADBBDCAAAABDADBDDABDBBAAACBDBADCBDDBDCDCCADCDCCADDCDCACADDAABCDCADBBCACDDADACADCCADBDDBADACAABDDADAAABADDBBAADADBCCABDDBCCDBBBAADABADADDDDDABBCDBDBBDABDCAABADCDADAAAADDCBCAADACDADACBBBDBDCBBDDBDADABCAAAADABDBBDABCDDDAADDAADDBDBCADCDAACBDCADBCADDACDCBDAAADABBBBDCBDDDDBBCADCDADDCCBDBAADDADDCDDBABDBDDAABDAADCCADDBDCDBDCABDBABBDCACACDCDADBDBAAAADADDDADAAACDBDABAAADBABDDABDDDAACCDBCADDDCDDADDBDACCADDADDDCCABBBCAAAADBDADDDDADDCADADDBADBBCDAADCCADBCBACCDABBDDDDDADCDDBADACAACDDADBCAAAADBDACADADABDABDBDDDDADBDDCCBACBDACABADDDCAADCAADDDCCADADADBBDCBBDBACDABACADBDADBAAABABDDDBBBDDADAADBCCABBDBDDA
Можно предположить различные схемы кодировки флага. Поскольку мы получили 4 символа, то возможна кодировка 4х пар битов. Или каждый символ кодирует 0 или 1. Тогда придется выбрать 2 рабочих символа, а 2 оставшихся – считать мусором и не рассматривать вообще.
Рассмотрим вторую схему. С двумя рабочими символами. Заменим их латинскими буквами A, B, C, D (они не встречаются в тексте). Остальные символы – удалим.
Получили несколько перемешанных битовых карт. А именно – 12 пар: AB, AC, AD, BA, BC, BD, и т.д. Первая буква – 0, вторая – 1.
Пишем скрипт для обработки:
s = 'ADADAC . . . BDDA' c1, c2 = "A", "D" s0 = "".join(["0" if x == c1 else "1" for x in s if x in c1 + c2]) print("".join([chr(int(s0[i:i+8], 2)) for i in range(0, len(s0), 8)]))
С битовой карты AD получили строку:
The Angel would tell me that lambs were not the color of tigers ... grodno{Now_I_know_that_neither_the_Angel_nor_Satan_spoke_the_truth}.
grodno{Now_I_know_that_neither_the_Angel_nor_Satan_spoke_the_truth}
Стего в офисе. III
Замечаем что между словами есть как один пробел так и два пробела. Считая один пробел за '0', а два пробела за '1', получим битовую строку. Попытка построить текст на основе такой кодировки и найти в нем флаг не дает результата.
Попробуем считать переходы. Переход от «пробела» к «двум пробелам» и наоборот. Т. е. «пробел» -> «два пробела» = 0, «два пробела» -> «пробел» = 1.
Для этого напишем скрипт на python:
s = "000..." s = "".join(['1' if s[i:i+2] == '10' else '0' for i in range(0, len(s), 2) if s[i] != s[i+1]]) print("".join([chr(int(s[i:i+8], 2)) for i in range(0, len(s), 8)]))
Получаем флаг:
grodno{Is_life_a_pursuit_of_a_chimera_or_a_search_for_truth?}
Стего в офисе. V
Из условия можно сделать предположение, что текст флага окрашен в цвет, отличный от цвета основного текста. Не будем выяснять в какой.
Просто удалим весь текст основного цвета стандартной функцией «Заменить» (этот цвет называется «Авто»):
Удалим в оставшемся тексте все пробелы и преобразуем в формат флага:
grodno{BorgesnumberisthenumberofbooksintheLibraryofBabel}
RED Heat
Для решения необходим оригинал постера.
Получив Red_Heat.jpg и Red_Heat.png, сравниваем их попиксельно.
В названии RED написано капсом, что намекает на значение красного в пикселях.
В полученных при сравнение отличающихся пикселях значение красного заменено ASCII кодом символов флага.
Код на Python:
grodno{aF4#gr8*';er!@QW}
Crypto
Дискалькулия
Чтобы создать безопасную среду для людей, страдающих дискалькулией, числа в названии шифра заменили на буквы. Что это за популярный шифр с числами в названии? очевидно, это шифр Цезаря со сдвигом. То есть "Шифр L" обозначает сдвиг 11 в шифре Цезаря. Если ссылку зашифровали сдвигом 11, то для расшифрования нужно применить сдвиг 26-11 = 15. Получаем эту ссылку, в которой и находится флаг
https://docs.google.com/document/d/1SgWpfxAGeYwBWyEdooPMryFH_TwhWkPO0ffxBw2ORSY
Коммутативность
Условие задачи намекает, что в ссылке каким-то образом переставлены символы. Тут я вспомнил что ссылки Google docs начинаются с символа "1" и как ни странно единица - второй символ в ссылке:
I1N85BPgD23qCMIIWbwQoHZKVJB11TVctD05MtPIums4
Тут же проверил вариант, где символы переставляются первый со вторым, третий с четвертым и т.д.
Можно было сделать вручную, но я написал короткий код:
def swap_pairs(string): swapped_string = '' for i in range(0, len(string), 2): swapped_string += string[i+1] + string[i] return swapped_string input_string = "I1N85BPgD23qCMIIWbwQoHZKVJB11TVctD05MtPIums4" result = swap_pairs(input_string) print(result)
И я оказался прав, ссылка успешно открылась!
https://docs.google.com/document/d/1I8NB5gP2Dq3MCIIbWQwHoKZJV1BT1cVDt50tMIPmu4s
Бег по кругу
Сразу пришло в голову сдвинуть каждый символ на определенное число позиций, например строка "qwerty" после одного сдвига будет выглядеть "wertyq". Как и в предыдущем задании, вспоминаем что ссылка начинается с "1". Тогда из ссылки
2sOeVtCFY3i3K9UEXqVWuoxvO-6X8LwE1Zp9J7D23ZOe
получаем
1Zp9J7D23ZOe2sOeVtCFY3i3K9UEXqVWuoxvO-6X8LwE
https://docs.google.com/document/d/1Zp9J7D23ZOe2sOeVtCFY3i3K9UEXqVWuoxvO-6X8LwE
Like a simple RSA
Рассмотрим функции шифрования и расшифрования.
c = (m*f)modh
m = (c*g)modh
Подставим с во второе уравнение и выполним некоторые преобразования:
m = (g*(m*f)modh)modh
m = (m*f*g)modh
По функции расшифрования понятно, что mmodh = m;
mmodh = (m*f*g)modh
1 = (f*g)modh
То есть, g - обратное f по модулю h число (мультипликативная инверсия по модулю).
Зная это свойство секретного ключа g, имея открытый ключ (h, f) можно с легкостью расшифровать сообщение:
from Crypto.Util.number import inverse def decrypt(c, public_key, private_key): m = (c * private_key) % public_key[0] return m f = 6416887830534433629567050229667684734973282397714251811755752025377169146409477365161229363756766441347368380751372023179641944107478195964183801916988232697767349642657617 h = 8470387347298476177456807592234800305223146039241805029722152502623609066137655800632000167660507958129073278126249206662322559690599099900670113252751054479281818630329178438136876189437058031446326100810231155131782952040600724 c = 4501577816736015596060497850546201260925821617971707200151522683849342994222685636584343269899367495933875369681542486925080096048706520947165012158736670485935607004216130611982263313862618848808033812929735326651289123228929207 g = inverse(f, h) M = decrypt(c, [h, f], g) print(M)
Полученное число преобразуем в hex, а потом в текст.
grodno{Asymmetric_encryption_uses_two_keys}