Здесь нам даны три функции:
- encrypt — зашифровывает переданный нами открытый текст.
- get_flag — проверяет переданный нами ключ, и если он совпадает с ключом шифрования, то мы получаем флаг.
- receive — расшифровывает переданный шифротекст и пытается декодировать байты в строку. При успешном декодировании мы получаем сообщение об успехе. Иначе — сообщение об ошибке и расшифрованный текст в хексах.
Значит здесь мы должны получить ключ шифрования и передать его в функцию get_flag. Как же нам это сделать? Здесь есть один очень важный момент — в качестве инициализирующего вектора ленивый программист использовал ключ шифрования:
Это создаёт уязвимость, с помощью которой мы легко можем получить ключ. Давайте разбираться как.
Посмотрим ещё раз на схему расшифровки:
Наше внимание здесь падает на операцию XOR. Что с ней не так? Как раз то, что вместо IV используется ключ:
Для того, чтобы понять, как нам это использовать, давайте введём переменную T — это текст после блока расшифровки, но до ксора с ключом. Тогда открытый текст P будет равен T ^ Key:
Совершенно очевидно, что Key = P ^ T. Но у нас нет значения T, а мы очень хотим его получить. Что можно сделать? Тут на помощь приходит схема CBC. Смотрите, если предыдущий блок шифротекста содержит исключительно нули, то следующий блок после расшифровки проксорится с ним, то есть останется неизменным:
Тут становится ясно, что если мы проксорим выход первого и третьего блоков, то получим ключ, который нам так нужен.
Атака будет выглядеть так:
- Зашифруем 3 блока текста (текст лучше создать из непечатных символов, чтобы наверняка получить ошибку в функции receive).
2. В полученном шифротексте заменяем второй блок нулями, а третий блок меняем на первый. То есть, если мы получили C1, C2, C3. То в функцию receive нам нужно передать C1, 0, C1.
3. Функция receive после расшифровки выдаст нам ошибку и расшифрованный текст (P1, P2, P3). Тут мы берём первый и третий блоки, и ксорим их, получая ключ, Key = P1 ^ P3.
4. Отдаём ключ в функцию get_flag и получаем флаг.