Се­год­ня мы возь­мем ста­рое при­ложе­ние на Visual Basic и поп­робу­ем разоб­рать­ся, как оно шиф­рует файл лицен­зии. Най­дем дек­риптор, вос­ста­новим алго­ритм по дизас­сем­бли­рован­ному коду и с помощью ИИ опоз­наем реали­зацию крип­тоал­горит­ма Skipjack. Заод­но выяс­ним, как сек­ретный аме­рикан­ский шифр вре­мен холод­ной вой­ны ока­зал­ся внут­ри обыч­ной прог­раммы и почему от его крип­тостой­кос­ти в ито­ге поч­ти ничего не оста­лось.

Мы про­дол­жим вспо­минать раз­ные экзо­тичес­кие крип­тоал­горит­мы вро­де EDDSA или Rabbit. Крип­тоал­горит­мов челове­чес­тво при­дума­ло великое мно­жес­тво, еди­ницы из них при­нима­ются в качес­тве стан­дарта, оста­ются и совер­шенс­тву­ются. Боль­шинс­тво же при­ходит и ухо­дит, оста­ваясь в народ­ной памяти исклю­читель­но бла­года­ря скан­далам и интри­гам, их соп­ровож­дающим.

Про один подоб­ный слу­чай ты, веро­ятно, пом­нишь по статье «Крип­тосамо­пал. Иссле­дуем кри­тичес­кие баги в самодель­ном крип­тоал­горит­ме и учим­ся обра­щать кас­томные поточ­ные шиф­ры» — в ней упо­мина­ется упраз­днен­ный по при­чине недос­таточ­ной крип­тостой­кос­ти алго­ритм GSM-шиф­рования A5/1. Как ты, воз­можно, догадал­ся, подоб­ные инци­ден­ты в исто­рии неред­ки, поэто­му про­дол­жим дан­ную тему.

Как обыч­но, нач­нем мы с пос­танов­ки задачи. Име­ется некое при­ложе­ние, реали­зован­ное на Visual Basic (мы раз­бирали подоб­ное в статье «Пре­пари­руем P-Code. Как ревер­сить ста­рый софт на Visual Basic»). Толь­ко сей­час мы име­ем дело с VB-кодом, ском­пилиро­ван­ным в натив, а инс­тру­мен­ты иссле­дова­ния исполь­зуют­ся те же. Тре­бует­ся изу­чить файл лицен­зии это­го при­ложе­ния, что­бы рас­шифро­вать его, ревер­сировать алго­ритм шиф­рования и смо­дели­ровать упо­мяну­тый алго­ритм. Файл этот тек­сто­вый, лежит в катало­ге при­ложе­ния и содер­жит в себе отно­ситель­но корот­кую шес­тнад­цатерич­ную стро­ку в вер­хнем регис­тре, типа вот такой:

0F300C42D8404EF23FC6F72A2DF008CFBC397DC6ED5C72AF0F5279D83509D94F

Пос­коль­ку мы уже дос­таточ­но под­кован­ны в ревер­се натив­ных при­ложе­ний на Visual Basic, при помощи IDA и VB Decompiler мы без осо­бого тру­да находим в коде адрес импорти­руемой из msvbvm60.dll фун­кции vbaLineInputStr, чита­ющей тек­сто­вую стро­ку из фай­ла. На нее мы и ста­вим точ­ку оста­нова в нашем любимом отладчи­ке x64dbg. При заг­рузке при­ложе­ния эта точ­ка оста­нова ожи­даемо сра­баты­вает, как толь­ко стро­ка лицен­зии чита­ется из тек­сто­вого фай­ла, и мы видим кон­текст ее вызова в коде, дизас­сем­бли­рован­ном в IDA.

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

По­ка все выг­лядит «на изи». На пос­леднем скри­не даже прос­матри­вает­ся оче­вид­ный кан­дидат на дек­риптор лицен­зии — вызов Proc_92_0_AC9420 со вто­рым стро­ковым парамет­ром "AbCdEfG", явно ключ шиф­рования. Сде­лав нес­коль­ко шагов в отладчи­ке x64dbg, мы убеж­даем­ся в пра­виль­нос­ти этой гипоте­зы: Proc_92_0_AC9420 воз­вра­щает тек­сто­вую стро­ку рас­шифро­ван­ной лицен­зии вида 31/07/2026//User//56387429120874480//AA000000000000003156//17F8L1M, раз­бирать которую в нашу задачу не вхо­дит.

А вот пер­вую часть нашей проб­лемы мы решили — лицен­зию рас­шифро­вали. Это была прос­то раз­минка перед гораз­до более слож­ной задачей: ревер­сировать алго­ритм дек­рипта. Я и не обе­щал, что будет прос­то, но забыл пре­дуп­редить — час­то исполь­зуемый в моих пре­дыду­щих задачах детек­тор извес­тных крип­тоал­горит­мов 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, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии