Мы продолжим вспоминать разные экзотические криптоалгоритмы вроде EDDSA или Rabbit. Криптоалгоритмов человечество придумало великое множество, единицы из них принимаются в качестве стандарта, остаются и совершенствуются. Большинство же приходит и уходит, оставаясь в народной памяти исключительно благодаря скандалам и интригам, их сопровождающим.
Про один подобный случай ты, вероятно, помнишь по статье «Криптосамопал. Исследуем критические баги в самодельном криптоалгоритме и учимся обращать кастомные поточные шифры» — в ней упоминается упраздненный по причине недостаточной криптостойкости алгоритм GSM-шифрования A5/1. Как ты, возможно, догадался, подобные инциденты в истории нередки, поэтому продолжим данную тему.
Как обычно, начнем мы с постановки задачи. Имеется некое приложение, реализованное на Visual Basic (мы разбирали подобное в статье «Препарируем P-Code. Как реверсить старый софт на Visual Basic»). Только сейчас мы имеем дело с VB-кодом, скомпилированным в натив, а инструменты исследования используются те же. Требуется изучить файл лицензии этого приложения, чтобы расшифровать его, реверсировать алгоритм шифрования и смоделировать упомянутый алгоритм. Файл этот текстовый, лежит в каталоге приложения и содержит в себе относительно короткую шестнадцатеричную строку в верхнем регистре, типа вот такой:
0F300C42D8404EF23FC6F72A2DF008CFBC397DC6ED5C72AF0F5279D83509D94F
Поскольку мы уже достаточно подкованны в реверсе нативных приложений на Visual Basic, при помощи IDA и VB Decompiler мы без особого труда находим в коде адрес импортируемой из msvbvm60. функции vbaLineInputStr, читающей текстовую строку из файла. На нее мы и ставим точку останова в нашем любимом отладчике x64dbg. При загрузке приложения эта точка останова ожидаемо срабатывает, как только строка лицензии читается из текстового файла, и мы видим контекст ее вызова в коде, дизассемблированном в IDA.

А вот это то же самое в более удобочитаемом VB-коде, восстановленном с помощью VB Decompiler.

Пока все выглядит «на изи». На последнем скрине даже просматривается очевидный кандидат на декриптор лицензии — вызов Proc_92_0_AC9420 со вторым строковым параметром "AbCdEfG", явно ключ шифрования. Сделав несколько шагов в отладчике x64dbg, мы убеждаемся в правильности этой гипотезы: Proc_92_0_AC9420 возвращает текстовую строку расшифрованной лицензии вида 31/, разбирать которую в нашу задачу не входит.
А вот первую часть нашей проблемы мы решили — лицензию расшифровали. Это была просто разминка перед гораздо более сложной задачей: реверсировать алгоритм декрипта. Я и не обещал, что будет просто, но забыл предупредить — часто используемый в моих предыдущих задачах детектор известных криптоалгоритмов Krypto Analyzer тут не работает, известных ему алгоритмов в нашем exe-файле не находит. Придется полагаться только на себя и искусственный интеллект. Декомпиляция Proc_92_0_AC9420 тоже особо не проясняет вопрос.

Из нее видно, что модуль шифрования носит очень говорящее название modEncryption и дешифрование происходит поблочно: шестнадцатеричная строка режется на блоки по 16 символов (что соответствует бинарным 64 битам) и каждый блок расшифровывается отдельно, преобразуясь опять же в шестнадцатеричную текстовую строку. Значит, придется копать вглубь: начнем с процедуры Proc_92_8_ACA9E0 — она стоит перед основным циклом дешифрования и имеет своим параметром строку‑ключ, a это сильно смахивает на инициализацию криптоалгоритма. В декомпилированном VB Decompiler начало ее выглядит как инициализация длинного (256 значений) массива строковых шестнадцатеричных констант, каждая из которых эквивалентна 8-битному значению.

Еще на один вопрос у нас появился ответ. Krypto Analyzer не детектирует этот алгоритм потому, что он предполагает: стандартные константы алгоритмов и их массивы хранятся в бинарном виде, а здесь, похоже, вся расшифровка идет через манипуляции с шестнадцатеричными текстовыми строками. Вдобавок исходный массив по извращенной воле разработчиков инициализируется поэлементно. Однако хорошего все равно мало — если мы покажем код инициализации нейросети, она вразумительного правдоподобного ответа не даст: в ее гипотезах это и AES, и CMEA, и даже ГОСТ, но по факту ни один массив точно не подходит. А значит, налицо злонамеренная модификация алгоритма в ущерб криптостойкости, примерно как было описано в статье «Криптосамопал. Исследуем критические баги в самодельном криптоалгоритме и учимся обращать кастомные поточные шифры».
Попытка зайти со стороны анализа, как получается расписание ключей, тоже особого толку не дает, скорее даже сбивает: его восстановленный код, преобразованный в C#, выглядит примерно так:
private static string[] BuildKeySchedule(string key){ string[] baseKey = new string[10]; for (int i = 0; i < 10; i++) baseKey[i] = key.Substring(i * 2, 2); string[] schedule = new string[132]; for (int i = 0; i < 132; i++) schedule[i] = baseKey[i % 10]; return schedule;}То есть ключ должен уложиться в 20-символьную Hex-строку, представляющую 10 байт (80 бит), которые, циклически повторяясь, заполняют расписание длиной 132 байта. ИИ не может дать однозначного определения криптоалгоритма, инициализирующегося подобным образом: алгоритмов с 80-битным ключом великое множество. Но главное сомнение вызывает то, что у нас ключ, напоминаю, AbCdEfG. Мало того что он не 20-символьный, вдобавок у него только некоторые символы с натяжкой могут быть интерпретированы как шестнадцатеричные цифры. По сути, мы снова сталкиваемся с какой‑то неуклюжей ламерской хитростью, призванной запутать нас в идентификации криптоалгоритма.
Похоже, нам не избежать разбора основной процедуры дешифрования 64-битного блока Proc_92_9_ACD970. Восстановленный код этой процедуры что из IDA, что из VB Decompiler (как и любой восстановленный VB-код) чудовищно громоздок и сложен для понимания простым человеческим интеллектом. По счастью, нам уже подогнали интеллект искусственный. Так же как и в прошлый раз, скармливаем ему восстановленный полубезумный код Proc_92_9_ACD970 и снова просим идентифицировать криптоалгоритм и преобразовать код в понятный нам синтаксис C#:
public static string DecryptBlockHex( string ciphertextHex, byte[] roundKeys, ){ ciphertextHex = ciphertextHex.Trim(); // Входное состояние: state[32] в оригинальном VB-коде ushort w1 = ParseUInt16Hex(ciphertextHex.Substring(0, 4)); ushort w2 = ParseUInt16Hex(ciphertextHex.Substring(4, 4)); ushort w3 = ParseUInt16Hex(ciphertextHex.Substring(8, 4)); ushort w4 = ParseUInt16Hex(ciphertextHex.Substring(12, 4)); /* dword_B00B00 = 32 dword_B00B04 = 31 Раунды: 32..25 : inverse Rule B 24..17 : inverse Rule A 16..9 : inverse Rule B 8..1 : inverse Rule A */ for (int step = 0; step < 32; step++) { int roundNumber = 32 - step; // 32…1 int keyRoundIndex = roundNumber - 1; // 31...0 ushort g = GInverse(w2, roundKeys, keyRoundIndex); ushort oldW1 = w1; ushort oldW2 = w2; ushort oldW3 = w3; ushort oldW4 = w4; bool inverseRuleB = step < 8 || (step >= 16 && step < 24); if (inverseRuleB) { /* x1 = G^-1(y2) x2 = G^-1(y2) XOR y3 XOR round x3 = y4 x4 = y1 */ w1 = g; w2 = (ushort)(g ^ oldW3 ^ roundNumber); w3 = oldW4; w4 = oldW1; } else { /* x1 = G^-1(y2) x2 = y3 x3 = y4 x4 = y1 XOR y2 XOR round */ w1 = g; w2 = oldW3; w3 = oldW4; w4 = (ushort)(oldW1 ^ oldW2 ^ roundNumber); } } return string.Format( "{0:X4}{1:X4}{2:X4}{3:X4}", w1, w2, w3, w4);}
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
