Ис­сле­дуя ста­рое при­ложе­ние на Delphi, мы вос­ста­новим алго­ритм шиф­рования, про­ана­лизи­руем работу генера­тора псев­дослу­чай­ных чисел и при­меним ата­ку на осно­ве извес­тных откры­тых тек­стов, что­бы извлечь кор­рек­тный регис­тра­цион­ный код без перебо­ра и без пат­чинга при­ложе­ния.

По сути, в этой статье мы раз­берем необыч­ный слу­чай реали­зации защиты, осно­ван­ной не на баналь­ном срав­нении серий­ного номера, а на рас­шифров­ке дан­ных с помощью регис­тра­цион­ного клю­ча.

Лю­бой опыт­ный хакер зна­ет: хорошая защита при­ложе­ния не дол­жна ломать­ся одно­бай­товым пат­чем. Неваж­но, нас­коль­ко код был обфусци­рован или вир­туали­зован, если защита прог­раммы сво­дит­ся к про­вер­ке какого‑то опре­делен­ного усло­вия (или даже нес­коль­ких усло­вий), то най­ти и запат­чить их — дело вре­мени, ква­лифи­кации хакера и инс­тру­мен­тов, име­ющих­ся в наличии.

Са­мое луч­шее, что мож­но сде­лать в дан­ном слу­чае, — мак­сималь­но усложнить жизнь потен­циаль­ному ревер­серу: как мож­но силь­нее запутать код, навесив на него кучу про­верок целос­тнос­ти и про­чих неп­рият­ных свис­телок, что­бы воз­ня с их поис­ком и обхо­дом ста­ла по зат­ратам тру­да и вре­мени (которые у хакеров, как извес­тно, недеше­вы) дороже, чем про­фит от сня­тия защиты. Чаще все­го так и дела­ют: это клас­сичес­кая вой­на щита и меча, одна­ко этот меч обо­юдоос­трый. Воп­реки рас­хожей фра­зе «ломать — не стро­ить», хакер, теоре­тичес­ки, прик­ладыва­ет для ревер­са гораз­до боль­ше уси­лий, чем раз­работ­чик для защиты при­ложе­ния.

Тем не менее, быва­ют вари­анты, ког­да защиту взло­мать невоз­можно в прин­ципе. Как ты уже догадал­ся, это слу­чай, ког­да у хакера не хва­тает какой‑то клю­чевой информа­ции, жиз­ненно важ­ной для пол­ноцен­ной работы прог­раммы. К при­меру (кста­ти, самый надеж­ный вари­ант), неко­его модуля, или прос­то кри­тичес­кого кус­ка кода, или дан­ных, под­гру­жаемых с уда­лен­ного сер­вера или, на худой конец, модуля, рас­шифро­выва­емо­го при помощи клю­ча, отсутс­тву­юще­го у хакера.

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

Итак, у нас есть некое вин­тажное при­ложе­ние, демонс­три­рующее в про­цес­се работы гра­фичес­кие дан­ные, набор которых без регис­тра­ции силь­но огра­ничен.

Ре­гис­тра­ция выпол­няет­ся при помощи вво­да регис­тра­цион­ного кода, при­чем этот код ничем не огра­ничен: неиз­вес­тна ни его дли­на, ни набор допус­тимых сим­волов, никакой валида­ции не про­изво­дит­ся, набирать мож­но все что угод­но. Пра­виль­ность вве­ден­ного кода мож­но про­верить, толь­ко переза­пус­тив прог­рамму.

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

Пер­во‑напер­во опре­делим харак­тер нашего при­ложе­ния, скор­мив его DetectItEasy — при­ложе­ние написа­но на ста­рень­ком Дел­фи, без вся­кой защиты:

PE32
Operation system: Windows (XP) [I386, 32-bit, GUI]
Linker: Turbo linker (2.25)
Compiler: Borland Object Pascal(Delphi) (10.0)
Language: Object Pascal(Delphi)
Library: Visual Component Library
Tool: Borland Delphi (3)
(Heur) Protector: Generic [High entropy]

В качес­тве инс­тру­мен­та для иссле­дова­ния прог­раммы выбира­ем, как обыч­но, Interactive Delphi Reconstructor (IDR). Раз­ложив в нем EXE-модуль, сра­зу же находим две про­вер­ки валид­ности регис­тра­ции, а так­же сооб­щение о необ­ходимос­ти регис­тра­ции при стар­те при­ложе­ния.

А еще пре­дуп­режде­ние о незаре­гис­три­рован­ной вер­сии при нажатии мышью на недос­тупную кар­тинку.

Ка­залось бы, вот он, тот самый слу­чай «одно­бай­тового пат­ча», ан нет, при пат­че выделен­ных условных перехо­дов (или даже про­веря­емых перед ними перемен­ных) отклю­чают­ся толь­ко пре­дуп­режда­ющие сооб­щения, нуж­ные фун­кции и отсутс­тву­ющие гра­фичес­кие дан­ные по‑преж­нему недос­тупны. Поэто­му про­дол­жаем копать даль­ше.

Про­ана­лизи­ровав код с пре­дыду­щих скрин­шотов, обна­ружи­ваем, что приз­нак отсутс­твия регис­тра­ции — зна­чение по умол­чанию регис­тра­цион­ного клю­ча «0123456789», которое наз­нача­ется при отсутс­твии дан­ного клю­ча в реес­тре. Попут­но находим прос­тень­кую валида­цию вве­ден­ного клю­ча для его записи в реестр при зак­рытии регис­тра­цион­ной фор­мы.

Как видишь, она пред­став­ляет собой при­митив­ную про­вер­ку на дли­ну клю­ча, которая дол­жна сос­тавлять не менее 3 сим­волов. Ты, уже, веро­ятно, догадал­ся, что прог­рамма не регис­три­рует­ся любым клю­чом длин­нее 3 сим­волов. Более того, такой ключ хоть и попада­ет в реестр, но, по какой‑то при­чине не заг­ружа­ется отту­да в прог­рамму при выпол­нении метода заг­рузки Form1 с пер­вого скрин­шота — на выходе все рав­но будет рег­код по умол­чанию 0123456789.

Поп­робу­ем разоб­рать­ся, отче­го так про­исхо­дит, вни­матель­нее про­ана­лизи­ровав код это­го метода. Сра­зу пос­ле заг­рузки кода из реес­тра с его пер­выми тре­мя сим­волами выпол­няет­ся такая стран­ная опе­рация.

Те­перь понят­но, почему перед сох­ранени­ем кода в реестр его про­веря­ли на трех­буквен­ную дли­ну, в перево­де на челове­чес­кий псев­докод это выг­лядит так:

dword_45291C = regcode[0]+regcode[2]+regcode[0]*regcode[1]

Ищем, где в даль­нейшем исполь­зует­ся перемен­ная dword_45291C, и тут начина­ется самая инте­рес­ная часть нашего квес­та. Ока­зыва­ется, это зна­чение ини­циали­зиру­ет зер­но (RandSeed) дель­фов­ско­го генера­тора псев­дослу­чай­ных чисел System.@RandInt. Это линей­ный кон­гру­энтный генера­тор (LCG, под­робно прин­цип такого генера­тора опи­сан в Википе­дии). Он пред­став­ляет собой внут­реннюю реали­зацию дель­фов­ской фун­кции Random. При каж­дом вызове она берет текущее зна­чение гло­баль­ной перемен­ной System.RandSeed, умно­жает его на кон­стан­ту 134775813 и при­бав­ляет 1.

Но самая мякот­ка зак­люча­ется в том, для чего, собс­твен­но, исполь­зует­ся этот псев­дослу­чай­ный генера­тор. А исполь­зует­ся он внут­ри кас­томно­го заг­рузчи­ка рас­тро­вых изоб­ражений при­ложе­ния Unit1.TForm1.Image.

Ока­зыва­ется, все‑все‑все свои рас­тро­вые дан­ные при­ложе­ние хра­нит в одной базе, в которой кар­тинки, скры­тые от глаз незаре­гис­три­рован­ного поль­зовате­ля, зашиф­рованы регис­тра­цион­ным кодом. А в качес­тве про­вер­ки кор­рек­тной регис­тра­ции исполь­зует­ся зашиф­рован­ная тес­товая кар­тинка — при ее кор­рек­тной рас­шифров­ке прог­рамма счи­тает­ся зарегис­три­рован­ной, а в про­цес­се обра­бот­ки исклю­чения, воз­ника­юще­го при сбое заг­рузки некор­рек­тно рас­шифро­ван­ного JPEG, регис­тра­цион­ный код сбра­сыва­ется в дефол­тное зна­чение.

Пос­коль­ку нам неиз­вестен не прос­то сам код, но и набор воз­можных сим­волов, из которо­го он сос­тоит, и даже его дли­на, то ситу­ация скла­дыва­ется доволь­но печаль­ная. Если раз­работ­чик все сде­лал по уму, то на этом эта­пе задачу пора бы уже и бро­сать, как бес­пер­спек­тивную. Одна­ко, как ты уже догадал­ся, решение у задачи все‑таки есть. Хва­тит посыпать голову пеп­лом, давай вмес­то это­го вни­матель­но пос­мотрим на алго­ритм рас­шифров­ки:

do
{
ReadBuffer((int)v3, (int)v23, 1);
LOBYTE(v23[0]) ^= RegCode[v19];
LOBYTE(v23[0]) ^= 9u;
LOBYTE(v23[0]) ^= RandInt(0xFFu);
WriteBuffer(*(int *)((char *)v23 + 1), (int)v23, 1);
if ( (unsigned __int8)RegCodeLength == v19 )
v19 = 0;
++v19;
--v11;
}
while ( v11 );

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

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

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

    Подписаться

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