Назад до списку статтей

Розробка пристрою, тестування, текст : (C) VetaSoft, 2003

Переклад в HTML: (C) VetaSoft, 2004

(C) Донецький Нацiональний Технiчний Унiверситет, 2004

Активний ключ для захисту програмного забеспечення від нелегального запуску.

 

Розроблений програмно-апаратний комплекс для захисту програмного забеспечення від нелегального запуску призначений для здійснення функції контролю ліцензії копії будь-якого програмного забеспечення. Комплекс включає дві відносно незалежні частини : програмну та апаратну. Контроль ліцензії копіі здійснюється програмним забеспеченням комплексу. Програмне забеспечення поставляється в *.dcu файлі, який являє собою результат компіляції (в середовищі Delphi 6.0 ) ісходного коду програмного забеспечення комплексу. Для здійснення функції контролю на етапі розробці якоїсь програми або програмного модуля програміст повинен включити до свого проекту цей файл (наприклад, в сеердовищі Delphi 6.0 це робиться за допомогою директиви Uses ) та десь у своєі програмі викликати на виконання головну функцію комплексу. Функція виконує оцінку та повертає певний результат. Результат може приймати 3 наступні форми :

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

 

. . . . . . . .

Uses USB_function; // підключення модулю

. . . . . . . .

Begin

. . . . . . . .

case (usb_rating(. . . )) of // виклик функції

#1:•••••• // аналіз результату

. . . . . . . .

End;

. . . . . . . .

Примітка : тип результату, параметри функції та значення, що повертає функція детальніше розглянуті далі.

 

Програміст може викликати на виконання функцію USB _ rating не один раз та в будь-якому місті свого програмного коду. Це забеспечить надійність стійкості його програми до злому.

 

 

Основні функціі, які виконують програмна та апаратна частини комплексу захисту.

 

Як було сказано раніше, комплекс захисту складається з двох частин : програмної та аппаратної. Оскільки апаратна частина являє собою активний пристрій, то вона повинна виконувати зберігання та передачу еталонного ключа. Ключ являє собою деяку послідовність довжиною 63 байта. Таким чином, основна функція апаратної частини комплексу нескінченна передача ключа по шині USB . При цьому программна частина опитує відповідний порт USB , та виконує порівняння (в найпростішому випадку) прийнятого (еталонного) ключа із тестовим. В більш складному випадку, який і реалізован у комплексі, програмна частина виконує порівняння чарунок пам’яті, адреса яких приймається із апаратного пристрою. Це необхідно для забеспечення надійності комплексу від злому.

 

Взаємозв’язок програмної та апаратної частини комплексу

 

Розглянемо, як відбувається взаємозв’язок програмної та аппаратної частини комплексу (рис.1). Шина USB має в своєму складі 4 лінії, дві з яких є інформаційними ( D -, D +), та дві забеспечують живлення пристроїв, які під’єднуються до шини ( VCC , GND ) [1]. Лінія GND є землею, або загальним проводом. На лінії VCC формується постійна напруга +5В, яка використовується для живлення апаратної частини. Це, безумовно, спрощує схему апаратної частини, а також вносить зручність при користуванні пристроєм: не порібне окреме джерело живлення, оскільки живлення апаратного пристрою здійснюється від блоку живлення комп’ютеру. Інформаційна лінія D - згідно із спеціфікацією на шину USB , є інверсією лінії D +. Слід мати на увазі, що на рис. 1 апаратний пристрій під’єднано до порту 0, але його можна було під ’ єднати й до порту 1. Для зв ’ зку з програмним забеспеченням у функції usb _ rating використовується параметр Num _ port , який зада є номер порта, до я кого п ідключен пристрій.

 

Апаратна частина комплексу нескінченно передає один ключ, або группу ключей – все залежить від того, як буде виконана прошивка ПЗП (постійного запам ’ ятовуючого пристрою), який мається у складі апаратної частини комплексу. Ключ передається послідовно (молодшими бітами вперед) байт за байтом ; таким чином передаються усі 63 байти. Далі передається 0 – це озна початку нового (або того ж самого) ключа. Після цього процес повторюється.

Ключ поступає через інтерфейс USB на один з портів( 0 чи 1) хост-контролеру USB ( на рис. 1 – на порт 0). У складі контролера USB є два однотипні регістри – PORTSC 0 (для порта 0) та PORTSC 1 (для порта 1), в яких біт 4 відповідає за поточний стан лінії D +, а біт 5 відповідає за поточний стан лінії D -[1]. Таким чином, для того, щоб програмна частина комплексу змогла прийняти ключ – вона повинна опитувати ці два біти – це і є функція опросу програмного забеспечення (детальніше ця функція розглянута в пункті 1.4). Треба сказати, що ці два біти доступні тільки для операції читання, а тому неможливо написати, наприклад, програмний емулятор, який б емулював роботу апаратної частини захистного комплексу – підвищується стійкість комплексу проти злому.

Для організації передачі ключа апаратним пристроєм використовується розроблений автором протокол обміну, який припускає передачу послідовно – однотипних даних по послідовній шині. Організація протоколу показана в таблиці 1.

Таблиця 1. Організація протоколу обміну.

СТ [1]

CT[0]

N

0

0

1

0

1

1

1

0

0

1

1

B

 

 

У таблиці наведені такі скорочення :

C Т[0], СТ[1] – молодші розряди лічильника тактів (розглянуто в пункті 1.3)

В – б іт, який передається.

 

З таблиці видно, що один біт ключа передаватиметься за 4 системних такти роботи пристрою. Це, безумовно, недолік протоколу обміну, але при використанні іншіх протоколів не забеспечується необхідна надійність передачі.

 

 

Апаратна частина комплексу захисту

 

Розглянемо роботу апаратної частини по структурно-функціональній схемі, яка зображена на рис. 2. Схема складається з таких блоків: сінхрогенератор, лічильник на 16 розрядів, запом’ятовуючий пристрій, де зберігається ключ (ПЗП), мультіплексор, блок інтерфейсу, схема організації роботи протоколу передачі інформації.

Сінхрогенератор представляє собою схему, що призначена для вироблення імпульсів сінхронізації, необхідних для одночасного спрацьовування усіх блоків пристрою. Від частоти, яка ним виробляється, залежить, у першу чергу, швидкість передачі ключа. Оскільки в схемі використовується нестандартний протокол обміну, треба обирати частоту в межах, які не перевищують частоту кадрів для шини, інакше контролер USB перестане реагувати на змінення на інформаційних ліниях D -, D +, і інформація загубиться. Частота кадрів для шини USB спеціфікації 1.1 – 1000 Гц. У пристрої частота синхрогенератору дорівнює 800 Гц.

Лічильник на 16 розрядів використовується для ліку сінхроімпульсів, які надходять з сінхрогенератору, та організації формування адреси для ПЗП. Лінії СТ [5]- CT [15] надходять до адресних входів ПЗП. Таким чином організовано перебір пам ’ яті ПЗП.

ПЗП представляє собою простір пам ’ яті обсягом 4 кб, в якому записано 64 або 32 ключа (при розмірі одного ключа відповідно 64 або 128 байт). Структура ключа наведена в таблиці 2.

Таблиця 2. Структура ключа при розмірі 64 байта

Адреса

0

1

...

3F

000

0

K1

K63

140

0

K64

K126

FC0

0

K 3969

K 4032

 

Тут : К i – байти ключей.

 

Треба сказати, що ПЗП повинен знаходитися на з’ємній панельці, звідки при необхідності його можна завжди зняти та виконати перепрошивку (при зміні ключа). Прошивка ПЗП здійснюється за допомогою стандартних програматорів, опис яких можна знайти в [2].

Байти ключей повинні лежати в межах 1..254, оскільки 0 є признаком початку ключа, а 255 – признак відсутності мікросхеми ПЗП (див. далі).

 

Обраний з ПЗП байт потрапляє на входи мультіплексору, який переводить паралельний сигнал у послідовний. Для цього на адресні входи мультіплексору потрапляють відповідні виходи лічильнику (СТ [2]- СТ [4] ) .

Далі сигнал з мультіплексору потрапляє на схему організації протоколу, яка реалізує таблицю істинності (див. табл. 1) розробленого автором протоколу передачі інформації. Виходи схеми потрапляють на інтерфейс USB .

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

На рис. 3 зображена принципова схема апаратної частини програмного комплексу захисту. При сучасному розвитку мікропроцессорної техніки всю схему, разом із ПЗП, можна було б реалізовати на спеціалізованному мікроконтроллері, наприклад К1816ВЕ51. Але при розробці принципової схеми враховувалась можливість її повторення починаючим радіоаматором. Тому автором обран варіант, який потрібує для реалізації декілька відносно дешевих мікросхем серії К561 та ПЗП серії К573. Мікросхеми серії К561, крім того, виготовлені по КМОП-технології та характеризуються невеличким енергоживленням.

Сінхрогенератор зібрано по стандартній схемі на елементах D 1.1, D 1.2. Частота, яка ним, виробляється, залежить від параметрів елементів R 1 та C 1. Вона обчислюється по формулі f =1/3 R 1 C 1 та при вказаних на схемі параметрах дорівнює приблизно 800 Гц.

Сигнал синхронізації з виводу 4 D 1.2 потрапляє на вход лічильника, зібраного на елементах D 2.1, D 2.2, D 3.1, D 3.2. Усі лічильники з ’ єднані послідовно в один 16 – ти розрядний лічильник. Таким чином, на виході лічильнику сигнал послідовно змінюється – від коду 0000 до FFFF . Старша частина цього коду потрапляє на адресні входи (А0 – А10) мікросхеми ПЗП, звідки считується ключ або група ключів. Адресний вход А11 мікросхеми ПЗП керується перемичкою П2 – це додатковий секрет пристрою. Якщо перемичка вставлена у відповідні гнезда Х2.1-Х2.2, то на цей вход потрапляє логічний нуль – в циклі опиту ключів опитується тільки нижня половина простору ПЗП, якщо перемичка відсутня – верхня.

Ключ байт за байтом считується з мікросхеми ПЗП ( D 4 ) та потрапляє до мультіплексору на елементі D 5, де перетворюється в послідовну форму. З виходу мультіплексору (вивід 14 D 5 ) ключ потрапляє до схеми реалізації протоколу, яка зібрана на елементах D 1.3, D 3.1. Вона реалізує таблицю істинності (див. табл. 1). З виходу схеми (вивід 10 D 1.3 ) сигнал через захистний резистор R 13 потрапляє на лінію D + шини USB . Для надійності спрацьовування сигнал додатково інвертирується на елементі D 1.4 та через захистний резистор R 1 4 потрапляє на лінію D - шини USB .

Якщо мікросхеми ПЗП в пристрої не буде взагалі, то на всі входи мультіплексору D 5 через резистори R 3 – R 10 потрапить логічна одиниця, тобто сгенерується код 255 – відсутність мікросхеми ПЗП. Цей факт зафіксує програмне забеспечення, якщо виконати в цей момент функцію usb _ rating , а крім того й блок інтерфейсу.

Блок інтерфейсу зібрано на транзисторі VT 1, світодіодах HL 1, HL 2, резисторах R 2, R 11, R 12. Світодіод Н L 1 червоного кольору загоряється, коли на пристрій подається живлення – він свідчить про те, що пристрій роботає. Світодіод Н L 2 зеленого кольору горить тільки, якщо мікросхема ПЗП вставлена. Оскільки код 255 заборонено у використанні складового елементу ключа, то на одному з виходів мікросхеми завжди буде логічний нуль. Тоді транзистор VT 1 буде знаходитися в стані відсічки, тому світодіод горить. В противному випадку транзистор переключиться у стан насичення – світодіод погасне.

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

Програмна частина комплексу захисту

 

Розглянемо роботу програмної частини комплексу захисту по блок-схемі, яку зображено на рис. 4. Програмна частина починає свою роботу з виклику функції USB _ RATING , яка викликається в той момент, коли программі, яка захищається, треба знати, що вона виконується легально. Звичайно це робиться на початку ії роботи, але можуть бути й інші варіанти. Функція USB_RATING має такі параметри :

USB_RATING (num_port : integer; key: key_data; ran: integer);

Де : num _ port – задає номер порта, до якого під ’ єднано пристрій (0 чи 1). Цей параметр задається у вигляді константи на етапі інсталяції програми, яка захищається. На етапі інсталяції також під ’ єднується апаратний пристрій.

Key – еталонний ключ у зашифрованному вигляді. Шифрування ключа виконується на базі методу шифрування RC 4. Тип цієї змінної має наступний вигляд :

Key _ data : array [1..64] of byte ;

Rand – випадковий параметр, який використовується у функції для формування формування випадкового результату. Цей параметр введено для складності злому програмного забеспечення комплексу захисту.

Функція USB _ RATING починає свою роботу з того, що виконує ініціалізацію усіх змінних (на рис. 4 відображено тільки основну змінну COL _ DEVICE ) – блок 1. Далі організовано цикл по пошуку усіх контролерів USB , які під ’ єднано до шини PCI по заданному наперед коду виробника та коду самого пристрою. Усі ці коди є стандартними, їх докладно розглянуто в [1]. Для контролера USB код виробника 0 B 103 h , код пристрою – 0С0300 h . Пошук поточного пристрою PCI проводиться в блоці 2 і якщо це контролер USB ( ця перевірка виконується в блоці 3 ) змінна COL _ DEVICE збільшується на одиницю ( блок 5) . Цикл півторюється, доки не будуть пересмотрені усі пристрої, під ’ єднані до шини PCI .

Далі у блоці 4 виконується перевірка змінної COL _ DEVICE . Якщо вона дорівнює нулю – це означає, що в циклі пошуку не було знайдено контролерів USB . При такому варіанті робота комплексу захисту неможлива, тому в блоці 14 формується результат помилки, який і повертає функція USB _ RATING .

Якщо ця змінна перевищує одиницю, то це означає, що знайдено більш одного контролера USB . В реальній конфігурації системи таке неможливо (якщо тільки якимось чином в системі не виявиться дві материнські плати). Цей варіант говорить про те, що в системі маються серйозні пошкодження операційної системи або апаратної частини комп’ютеру. В такому випадку в блоці 14 сформується інший результат помилки, який і поверне функція USB _ RATING .

У блоці 6 виконується запам ’ ятовування основних параметрів знайденого контролеру : номер шини, номер пристрою, під яким контролер зарегістровано на шині PCI та інші параметри. Після цього в блоці 7 виконується опрос контролеру з тим, щоб отримати базову адресу блоку регістрів контролеру. Ця базова адреса необхідна для того, щоб розрахувати адресу регістру відповідного порта (0 чи 1) – для цього до базової адреси додається зсув 10 h або 12 h (блок 8) – в залежності від параметру num _ port .

Усі вишчеозначені блоки (1-8), а також механізм контролю помилок (блок 14) реалізовано у функції INIT _ USB , текст якої наведено нижче.

{-------------------------------------------}

function InitUsb:boolean;

var col_con :byte; {Кількість контролерів USB}

usb_y :boolean;

begin

USBHostIndex:=0;

col_con:=0;

usb_y:=true;

while true do

begin

findUSBHostController;

if ((searchresult<>0) and (col_con>=0)) then

begin

showmessage('Контролери USB відсутні');

usb_y:=false;

break;

end;

if ((searchresult=0) and (col_con>0)) then

begin

showmessage('Контролер USB в системі не один. '+

'Програма не розрахована на работу з декількома '+

'контроллерами.');

usb_y:=false;

break;

end;

if ((searchresult=0) and (col_con=0)) then break;

inc(col_con);

end;

initusb:=usb_y;

end;

{-------------------------------------------}

У функції використовується процедура findUSBHostController, текст якої наведено нижче:

procedure findUSBHostController;

begin

asm

pushad

mov [SearchResult],0

// Знайти перший USB-контролер по коду класу

mov AX,0B103h

mov ECX,0C0300h

mov SI,[USBHostIndex]

int 1Ah

jnc @@ReadPCIRegisters //пристрій знайдено

// Вихід: контролер USB не знайдено

mov [SearchResult],1

jmp @@m_kon

// Пристрій знайдено, його координати на шині PCI

// знаходятся в регістрі BX

@@ReadPCIRegisters:

// Запам ’ ятати координати контролера

mov [Bus_Num],BH

mov [Dev_Num],BL

// Одержати ідентіфікатор виробника

mov AX,0B109h //читати слово

mov DI,0 //зсув слова

int 1Ah

jc @@BadRegisterNumber

mov [Vendor_ID],CX

// Одержати ідентіфікатор пристрою

mov AX,0B109h //читати слово

mov DI,2 //зсув слова

int 1Ah

jc @@BadRegisterNumber

mov [Device_ID],CX

// Одержати базову адресу блока регістрів контролера USB

mov AX,0B10Ah //читати подвійне слово

mov DI,20h //зсув слова

int 1Ah

jc @@BadRegisterNumber

// Обнулити 5 молодших бітів

and CX,0FFE0h

// Зберегти тільки молодше слово адреси

mov [Base_Addr],CX

// Вихід: контролер знайдено

jmp @@m_kon

// Невірний номер регістру

@@BadRegisterNumber:

mov [SearchResult],2

@@m_kon:

popad

cmp [SearchResult],0

jne @@m_end

inc [USBHostIndex]

@@m_end:

end;

end;

Далі у блоці 9 виконується опрос обраного порту статуту. Оскільки максимальна довжина ключа складає 63 байти та робота апаратної та програмної частини ніяк не сінхронізована, то в найгіршому випадку потрібно прийняти з пристрою 127 байт (подвійну довжину ключа плюс байт, який характеризує початок ключа, або перший байт ключа). Лише в такому випадку можна із впевненністю сказати, що серед цих 127 байт ключ буде як мінімум в одному екземплярі. Таким чином з порту потрібно прийняти 127*8=1016 біт інформації. Функція опросу, яку реалізовано в блоці 9, представляє собою цілий алгоритм для реалізації протоколу обміну, який було розглянуто раніше. Як видно з таблиці 1, прийняті 0 чи 1 характеризуються проміжком часу, який залежить від частоти сінхрогенератору апаратного пристрою. Чому дорівнює цей проміжок несуттєво, але суттєво, що проміжок для одиниці в два рази меньше, ніж проміжок для нуля. Виходячи із цього факту, програмне забеспечення й аналізує дані, які надходять з апаратної частини комплексу захисту та формує спочатку біти тестового ключа, а потім байти – це робиться в блоці 10.

Таким чином результатом роботи блоку 9, алгоритм якого реалізовано у функції oprosusb є значення типу longword , в якому зберігається вказаний проміжок часу. Слід сказати, що це значення може мати погрішність до 15%, оскільки схема сінхрогенератору не обладає достаточною точністю, але при аналізі та формуванні байтів (блок 10) це не впливає на точність передаваємої інформації :

Текст функції, яка реалізує алгоритм, наведений в блоці 9, приведено нижче

{-------------------------------------------------}

function oprosusb:longword;

var aa:longword;

{Функція виконує опит біта, починаючи з поточного моменту}

Begin asm

mov dx,[base_addr]

add dx,10h

mov ah,0

mov al,[num_kanal]

shl ax,1

add dx,ax // в Dx - базова адреса порта

@mm1: in ax,dx // чекання поточної 1

and ax,$10

cmp ax,0

je @mm1

{-----}

@mm2: in ax,dx // чекання поточного 0

and ax,$10

cmp ax,0

jne @mm2

{------}

mov ebx,0

@mm3: inc ebx

in ax,dx // чекання поточної 1

and ax,$10

cmp ax,0

je @mm3

mov [aa],ebx

end; oprosusb:=aa; End;

{-------------------------------------------}

Далі наведено текст функції, яка реалізує аналіз та формування тестового ключу (блок 10).

{-----------------------------------------}

function analisbit(num:word):boolean;

var i:word; bb:boolean;

data_0,data_1:longword;

Begin

bb:=true;

data_0:=data_buf[1]; data_1:=data_buf[1];

for i:=2 to num do

if data_buf[i]<data_0 then data_0:=data_buf[i];

for i:=2 to num do

if data_buf[i]>data_1 then data_1:=data_buf[i];

if (data_1-data_0)<((data_1+data_0)/8) then

Begin

bb:=false;

analisbit:=bb;

exit;

End;

for i:=1 to num do

if data_buf[i]<data_0+(data_0 div 2) then data_buf[i]:=0

else data_buf[i]:=1;

// формування тестового ключа

k=0;

for i:=1 to num do

begin

if i mod 8 = 0 then inc(k);

key_data[k]:=key_data[ k] shl 1;

key_data[k]:=key_data[k] or data_buf[i];

end;

analisbit:=bb;

End;

Таким чином, після відпрацьовування функції analisbit в глобальному масиві key _ data маємо послідовність з 127 байт в тому вигляді, в якому вона була передана з апаратної частини комплексу захисту.

У блоці 11 виконується пошук початку ключа. Цей алгоритм довольно простий, тому коментарі тут непотрібні. Скажемо лише, що, якщо признак початку ключа не знайдено (а ця ситуація може возникнути, або якщо пристрій під ’ єднано не до того порту, з якого проводиться опит, або по технічним причинам пристрій роботає неправильно, або, якщо мікросхема ПЗП до пристрою не під ’ єднана) то потрібно формувати код помилки (передавати управління в блок 14) та аварійно завершати роботу.

Блоки 12, 13 передбачають роботу з еталонним ключем, який передається в функцію USB _ RATING в зашифрованому вигляді. Шифрація еталоного ключа необхідна для захисту комплексу від злому. Шифрацію виконує програміст, який захищає свій програмний продукт за допомогою утіліти, яка додається до цієї розробки. Ключ обирається програмістом довільно, але з вриховуванням тих вимог, які було розглянуто в пункті 1.2.

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

Блок 14, виходячи з цього признаку, формує результат, який повертається через параметр функції USB _ RATING . Ісходний текст цього блоку теж не розглядається, але розглядаються основні принципи формування результату.

Формування результату здійснюється по наступному закону :

USB_RATING=ERROR+FUNC(RAND)+GETSYSTIKS;

 

Де :

ERROR – загальна кількість помилок, які було сформовано у блоці 14.

RAND – випадковий параметр, який потрапляє до функції

FUNC – деяка функц ія формування результату. Ця функція відома тільки програмісту, який захищає свій програмний продукт.

GETSYSTIKS – системна функція Windows , яка повертає кількість тактів системного таймеру, які пройшли з початку 1 січня 1980 року.

Передбачення цієї функції в результати, який має тип LONGWORD , є додатковим захистним засобом, якщо программіст бажає захистити свою программу не тільки від взлому, а й від трасування різними дізассемблерами ( IDA , TURBO DEBUGGER , WINHEX та ін.). Це вскладнює алгоритм, але підвищує стійкість комплексу захисту проти злому. В цьому випадку алгоритм захисту буде відрізнятися від алгоритму, розглянутому в пункті 1. Він може мати, наприклад, такий вигляд :

. . . . . . . .

Uses USB _ function ; // підключення модулю

. . . . . . . .

Var ss,result:integer;

Begin

. . . . . . . .

ss:=random(10000);

result:=usb_rating(0,keydat,ss) // виклик функції

ss:=FUNC(ss);

result:=result-getsystemtiks-ss;

case result of

#1:•••••• // аналіз результату

. . . . . . . .

End;

. . . . . . . .

Тепер, якщо між викликом USB _ RATING та GETSYSTEMTIKS пройде багато часу (це означає, що програма роботає покроково), то значення змінної RESULT стане дуже великим. Цей випадок і може проконтролювати програма, яка захищається.



Назад до списку статтей       На початок