Пастка для зломщика

Станіслав Короткий, Світ ПК

У наш вік інформації і комп'ютерів ПЗ стало одним з самих ходових товарів. Проте його, на відміну від звичайних, «відчутних» продуктів, як відомо, досить просто вкрасти. Ця проблема вже давно змушує програмістів піклуватися про захист своїх розробок від нелегального копіювання.


Можна сказати, що злом засобів захисту ПЗ став сьогодні свого роду професією або навіть більше того – покликанням. Багато молодих людей вважають його найкращим засобом самоствердження, не кажучи вже про те, що це дуже захоплююче заняття. Вони називають себе хакерами (hackers, від дієслова hack – «рубати», «шматувати») або кракерами (crackers, від дієслова crack – «ламати»)1.
Судячи з усього, протистояння розробників ПЗ і зломщиків збережеться і в осяжному майбутньому, а тому проблема «вандалостійкому» захисту не втратить актуальності. У даній статті ми розглянемо деякі технічні та психологічні аспекти її створення на платформі Windows-Intel.

Філософія захисту

Перш за все потрібно сказати, що хакер – не надприродна істота, а звичайний чоловік, і якщо розробник спроможеться вивчити методику його роботи, написання ефективного захисту перестане бути нерозв'язною завданням. Як правило, хакер добре знає зворотне проектування (reverse engineering), але відносно погано (в порівнянні з розробником) – системне програмування та математику. Крім того, він використовує деякі специфічні інструменти2, З якими розробник зазвичай не знайомий, і працює не з вихідним, а з двійковим кодом.


Але не варто і применшувати здібності хакера. Це гідний противник, не прощає ні наївних схем захисту, ні прорахунків в її реалізації. Зазвичай чомусь забувають, що загальна стійкість захисту визначається найслабшим її ланкою, так що будь-які хитрощі на кшталт апаратних ключів або прив'язки до комп'ютера будуть марними, якщо деяка частина алгоритму виконує тривіальну перевірку прапора «свій / чужий» на відповідність бажаного результату.


Фактично процес протистояння розробника і хакера носить динамічний характер, і завдання розробника – завжди бути на крок попереду. Кожна нова версія програми повинна мати нову схему захисту або, принаймні, настільки сильно модифіковану стару, щоб наявні у хакера напрацювання давали осічку. Звідси, до речі кажучи, випливає ще один важливий принцип: будь-яка схема захисту повинна існувати в єдиному екземплярі. Ні в якому разі не можна застосовувати один і той же алгоритм в різних програмах. Роблячи так, ви реально захищаєте тільки одну програму, а інші «здаєте без бою».


Слід пам'ятати, що абсолютно надійного захисту не існує. Будь-який захист можна зламати, тому необхідно шукати оптимальне співвідношення витрат на створення захисту і прогнозованих витрат на її злом, враховуючи також цінове співвідношення з самим захищається ПЗ.


Мабуть, єдиний спосіб надійно захистити свою програму на рівні ідеології – це перевести її з розряду ПЗ в розряд платформ. Під платформою тут розуміється досить складний програмний комплекс, експлуатація якого вимагає тісної співпраці з виробником. Зрозуміло, таке можливо тільки для високотехнологічних програмних систем корпоративного призначення. Але якщо ПЗ дозволяє змістити акценти в цей бік (що, наприклад, відбувається сьогодні з мультимедійними програмами, які плавно перетікають з домашніх комп'ютерів в офіси), то виробник отримує додатковий важіль для поліпшення захисту.

Метафізика захисту

Очевидно, що схема захисту повинна бути розрахована не стільки на виконання фіскальних функцій по відношенню до рядового користувача, скільки на протидію спробам хакера вивести з ладу даний фіскальний функціонал. Тому найпершим етапом в алгоритмі захисту, мабуть, варто назвати, образно висловлюючись, виявлення хакера. Звичайно, виявити можна не самого хакера, а інструменти, якими він користується. Звідси випливає висновок: перед тим, як писати який-небудь захист, розробник повинен з'ясувати, чим у даний момент користуються хакери (ясно, що через півроку ситуація кардинально зміниться, точно так само, як і на всьому ринку інформаційних технологій).


Як правило, в арсеналі хакера присутні кілька отладчиков, дизассемблеров і програм моніторингу системи (таких, як FileMon і RegMon). Всі вони, за винятком дизассемблеров, виконуються паралельно з програмою, а значить, можуть бути нею виявлені.


Найпростіший спосіб – виклик функції EnumWindows, яка перераховує створені в системі вікна верхнього рівня. Отримавши їх дескриптори, програміст може ідентифікувати запущені процеси (по заголовках вікон, назвам виконуваних модулів, інформації про версії файлів). Можна також скористатися «налагоджувальної» за своїм призначенням бібліотекою ToolHelp.


У Windows NT засобів для контролю за станом системи більше, ніж у Windows 95/98 (наприклад, там є спеціальна функція IsDebuggerPresent для виявлення отладчика). Оскільки приблизно з 2000 р. прогнозується масовий перехід на нову версію Windows NT, можна припускати, що розробники ПЗ скоро отримають додаткові кошти для боротьби з хакером.
Ще один популярний спосіб виявлення отладчика – це вимір часу виконання критичних ділянок коду, що дозволяє виявити покроковий режим роботи. Дуже корисно також перевірити, чи не поставлені Чи точки зупинки (байт 0xCC) на деякі функції API або самої програми: таким шляхом хакери часто намагаються визначити, в який момент створюється вікно реєстрації або перевіряються дані захисту. У лістингу показана перевірка на наявність точки зупину функції CreateWindowExA.

Перевірка наявності точки – зупину у функції CreateWindowExA

extrn  @CheckCRC$qv: FAR
extrn CreateWindowExA: FAR
public checkdeb; прототип – extern "C" int WINAPI checkdeb (void);
checkdeb proc near
push esi
push ds
push cs
pop ds
; Lea esi, @ CheckCRC $ qv; у разі прикладних
; Функцій Сі + + необхідно враховувати спотворення імен
; (name mangling)
lea esi, CreateWindowExA; функція, імпортована з DLL
mov esi, [esi +2]; для функції з DLL спершу
; Знаходимо її адресу в таблиці переадресації,
; Цей крок слід опустити в разі контролю
; Внутрімодульной функції
mov eax, [esi]; беремо перший байти функції
and eax, 0FFh; конкретно найперший
cmp eax, 0CCh; є точка зупинки?
mov eax,100h
je Debug_here
xor eax,eax
Debug_here:
pop ds
pop esi
ret; якщо все "чисто", повертаємо 0
checkdeb endp

Іноді цікаву інформацію можна отримати, вивчивши системне оточення за допомогою функції GetEnvironmentStrings.


Якщо виявлено відладчик або програма моніторингу, то постає питання про вибір схеми протидії. Активна схема припускає, що необхідно спробувати «завісити» ворожу програму або всю систему цілком, благо таких способів багато. Можна, наприклад, злегка зіпсувати стек або просто викликати функцію DestroyWindow для вікна отладчика. Більш екзотичні способи порушення штатної роботи небажаної програми, що залежать від особливостей її реалізації, можуть включати некоректне взаємодія з нею по протоколу DDE або COM: у першому випадку іноді достатньо передати сміття замість дескриптора блоку даних, у другому – зайвий раз (передчасно) викликати метод Release для використовуваного об'єкта.


Але активна схема протидії не позбавлена недоліків. По-перше, відладчики та монітори запускаються не тільки в злочинних цілях, так що в результаті контратаки може невинно постраждати законослухняний користувач. По-друге, виконуючи активні дії, захист виявляє себе і дає можливість локалізувати «вогнище опору».


Більш хитрої і, мабуть, дієвою є пасивна схема, коли захист при хакерській атаці просто-напросто вимикається (переводиться в «сплячий» режим). У результаті в хакера складається враження, що програма вже зламана або взагалі не містить захисту, але варто її запустити на «чистому» комп'ютері, як захист «прокидається».

Математика захисту

Як правило, захист оперує не менш як двома наборами даних, отриманими з різних джерел (наприклад, введені користувачем ім'я та прізвище і отриманий від розробника реєстраційний ключ). Щоб визначити, чи легально використовується програма (або в якому режимі вона використовується – демонстраційному або повнофункціональному), над цими наборами виконуються певні перетворення, і результати потім порівнюються. Зрозуміло, що відповідні фрагменти коду – головний об'єкт уваги хакера.


За великим рахунком хакер може застосувати при зломі два різних підходи: або, дослідивши алгоритми перетворення даних, навчитися самому генерувати реєстраційні ключі, які програма буде сприймати як правильні (це більш «престижний» метод), або просто зіпсувати захист – злегка «підправити» програму, так щоб захист перестала працювати або вважала б будь-якого користувача легальним.


Один з можливих способів боротьби зі спробами відтворити алгоритм генерації ключів – створення самомодифицирующихся коду. У цьому випадку статичний код, аналізований дизассемблером, не дає необхідних відомостей для відновлення алгоритму генерації реєстраційних ключів, і виникає необхідність аналізу програми в режимі налагодження. Якщо припустити, що ми успішно засікаємо відладчики і не активізуємо захист у їх присутності, то локалізувати потрібний фрагмент коду буде складно. А оскільки не можна перманентно модифікувати код, який динамічно формується тільки на стадії виконання програми, відключити захист теж стає неможливо або, принаймні, дуже важко (адже це потрібно робити для більшого обсягу коду в різних частинах програми).


Реалізувати самомодіфіцірующіеся код на мові високого рівня досить складно – краще використовувати асемблер. Можна, правда, обійтися і без модифікуючий коду, але тоді бажано, щоб перетворення даних під час перевірки прав користувача не зводилися до елементарних операцій.


Наприклад, розробники умовно-безкоштовних програм досить часто використовується для отримання реєстраційного ключа з імені користувача (або навпаки) складні комбінації побітових і цілочисельних арифметичних операцій. Однак настирного хакера такі вправи здебільшого тільки радують: утиліту реєстрації нелегальних користувачів він будує за кілька годин. Єдиний вихід – використовувати що-небудь більш «зубодробильні», скажімо шифрування з відкритим ключем або нетривіальні операції з плаваючою комою.


Схема шифрування може бути наступною. Розробник завчасно генерує пару ключів – відкритий і закритий. Користувач, що купив програму, посилає розробнику відомості про себе, а той, у свою чергу, шифрує їх за допомогою закритого ключа і відсилає зашифровані дані разом з відкритим ключем назад користувачеві. Вбудований в програму механізм захисту розшифровує за допомогою відкритого ключа інформацію, отриману від розробника, і порівнює її з наявними даними про користувача. Очевидно, що знання алгоритму дешифрування не дасть хакеру можливості створити реєстраційну утиліту: адже для неї необхідно не тільки відтворити алгоритм шифрування, але й знати закритий ключ розробника. Зрозуміло, надійність такого захисту залежить від криптостійкості алгоритму шифрування і довжини ключа. Windows NT, а також 98 і 95 починаючи з версії OSR2 містять програмний інтерфейс CryptoAPI, що дозволяє використовувати в захисті ПЗ комерційні системи шифрування, в тому числі з застосуванням пластикових карт і інших апаратних коштів.


Суть підходу, заснованого на нетривіальних математичних операціях, така. Нехай для перетворення інформації користувача X в реєстраційний ключ Y служить певна функція Y = F (X), визначена на просторі дійсних чисел; при цьому зворотна функція X = F-1 (Y), перетворююча реєстраційний ключ в інформацію про користувача, така, що її значення неможливо розрахувати виходячи з F (за досить тривалий період часу). Зрозуміло, що така ситуація приблизно аналогічна виникає при шифруванні з відкритим ключем.


Існує й інший варіант – вибрати функцію F складної форми і розраховувати її в алгоритмі захисту без використання запису в аналітичному вигляді. Як це здійснити? Наприклад, за допомогою нейронної мережі зворотного поширення. Розробник створює і навчає нейронну мережу на апроксимацію F, після чого навчена мережа впроваджується у фрагмент захисту. Ще більше ускладнити життя хакеру можна, розбивши F на два щаблі. У цьому випадку функція F запишеться у вигляді суперпозиції функцій H1 і H2. У схемі захисту необхідно апроксимувати функції H1 і H2-1, які будуть застосовуватися таким чином:
Z1=H1(X)
Z2=H2-1(Y)

Числа Z1 і Z2 виходять відповідно з інформації про користувача і перевіряється реєстраційному ключа. Їх співпадіння означає правомочне використання програми. Даний підхід ускладнює експлуатацію ще одного улюбленого методу роботи хакера, що полягає в «тупому» переборі значень реєстраційного ключа.


Зрозуміло, якщо код програми не самомодіфіцірующіеся, хакеру нескладно буде «виламати» з неї всю цю математику. Тут найбільш простим і в той же час ефективним способом боротьби є підрахунок контрольних сум для критично важливих ділянок коду.

Хронологія захисту

Досить часто захист ПО містить фрагмент, що відповідає за відстеження терміну пробної експлуатації програми. Ділянка коду, в якому час, що минув з моменту установки програми, порівнюється з дозволеним періодом безкоштовного використання, повинен захищатися за допомогою методів, аналогічних розглянутим раніше. Однак «хронологія захисту» має одне додаткове вразливе місце: хакер може модифікувати дату установки програми (або дату її останнього запуску – це іноді практикується для того, щоб запобігти, принаймні теоретично, дієвість трюків з перекладом системних годин). Ні реєстр, ні файлова система не гарантують «таємницю вкладів». Можна порекомендувати зберігати дату у відкритому чи закодованому вигляді, наприклад, у реєстрі і додавати до неї контрольну суму. У Windows NT є корисна функція RegQueryInfoKey, яка повідомляє дату створення ключа реєстру.

Хімія захисту

Наостанок наведу кілька нескладних правил, які дозволять розробнику «схимічит» при створенні захисту ПЗ.


Цей список кожен розробник легко продовжить сам. Багато свіжих ідей можна почерпнути з Internet та телеконференцій. Все це потрібно творчо переробити (щоб алгоритм захисту був унікальним, хоча і заснованим на відомих методах) і об'єднати – надмірність у захисті ніколи не заважає. Досить часто здаються прямо-таки ідеальними схеми захисту насправді легко зламуються за допомогою простого, але не врахованого програмістом прийому. Краще всього, звичайно, мати знайомого «чесного» хакера, який візьметься «поламати» вашу програму, щоб виявити слабкі місця захисту, але така можливість є не у всіх. Іншим залишається лише на свій страх і ризик прийняти умови гри і вступити в інтелектуальний протиборство з зломщиками.

1. Словом «Хакер» спочатку позначали програмістів зі специфічним «машинно-орієнтованим» стилем роботи, не обов'язково навіть займаються чимось протизаконним. Зараз так найчастіше називають тих, хто зламує сервери віддалених інформаційних систем (у той час як кракер – це саме фахівець зі злому програм), але термін цілком застосовний і до комп'ютерних зломщикам взагалі.

2. У більшості випадків розробник все-таки знайомий з інструментами хакера, але тільки не підозрює, що це вони і є, оскільки використовує їх не для злому, а для налагодження програми.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*