Защита, использующая хасп-ключ. Часть 2 — Архив WASM.RU

Все статьи

Защита, использующая хасп-ключ. Часть 2 — Архив WASM.RU

Пояснение ко второй части

  Данная статья является расширением к моей первой статье, посвященной анализу защиты этой программы ("Защита, использующая хасп-ключ").

  В первой статье был приведен поверхностный анализ защиты без исследования ее механизма: было приведено лишь поверхностное описание, которое также содержало ошибки (впочем, непринципиальные). В этой статье я собираюсь привести более детальное описание структуры защиты, полный код эмулятора отклика драйвера защиты, включающий в себя простейший эмулятор функций хасп-ключа. Вместе с этим хочу подчеркнуть, что ни в коей мере не претендую на уникальность исследования этой защиты: первенство, безусловно, принадлежит С-лю и M-o. Новым (возможно) является метод перехвата функций драйвера и обход контроля целостности драйвера.

  В первой моей статье программа исследовалась на уровне команд ввода-вывода (in/out) и приводилось описание того, как можно эмулировать отклик хасп-ключа на этом уровне: требуется создать лог ввода-вывода, проанализировать динамические части (меняющиеся от сессии к сессии) на предмет их эмуляции и научится избегать сброса отладочных регистров (DRx), применяемого защитой.

  В данном случае будет подробно описано, как именно устроен код, общающийся с ключом и реализующий так называемые "функции" хасп-ключа и исследован метод эмуляции не отклика самого ключа (команды ввода-вывода), но эмуляции функций хасп-ключа.

  Все исследования проводились в win98.

Общее описание структуры защиты

  Начну с пояснения по составу защиты. Во-первых, это, конечно, приложение (exe), а во-вторых это три драйвера. Из них только один (с постфиксом "DL") является "полезным" кодом, другой просто не вызвается, третий вызвывается, но от наличия или отсутствия ключа его поведение никак не меняется. Однако инсталлятор (тот, кто устанавливает или удаляет драйвер защиты) "нагло" оставляет его в папке \SYSTEM. А ведь он занимает достаточно много места (особенно поддерживающий версию 8 - 0,5 мегабайта) в оперативной памяти. Все три драйвера являются динамическими (загружаемые по запросу приложения но не на этапе инициализации системы).

  Итак, существует приложение и драйвер (VxD). Драйвер загружается в память посредством первого вызова CreateFile. Получив сообщение THREAD_INIT от системной машины, он устанавливает два обработчика исключений: #6 (UD - undefined opcode) и #14 - Page Fault. Приложение, в свою очередь, сознательно вызывает исключение UD путем примерно следующих конструкций:

  mov  eax,40000007h
  mov  ecx,Param1
  mov  edx,Param2
  ...
  db   0FEh,0EFh,00h,01h,02h,03h,04h,05h <<< Вызов исключения

  Обработчик исключения UD анализирует байты, вызвавшие исключение - буквально сравнением с "0FEh,0EFh,...,04h,05h" и трактует значение в регистре eax как номер функции. Всего поддерживается 11 функций: от 40000001h до 4000000Bh, однако реально вызваются не все, а только функции (здесь и далее в упрощенном до байта виде) 01,02,03,04,06,07 и 0B. Вот их предназначение:

Функция 1: Возврат в точку исключения + 8;
Функция 2: Возврат в точку исключения + 8;
Функция 3: Decrypt data:
             rol [each byte],4
             xor [each byte],0AAh,
           Возврат в точку исключения + 8;
Функция 4: Encrypt data:
             rol [each byte],4
             xor [each byte],0AAh,
           Возврат на адрес, указанный вызвавшим в регистре edi;
Функция 6: Decrypt data:
             xor [each byte],0AAh,
             ror [each byte],4
           Возврат на адрес, указанный вызвавшим в регистре edi;
Функция 7: Get CRC:
             add CRC_BYTE,[each byte]
           Возврат в точку исключения + 8;
Функция 0B: Execution hasp key functions:
           Выполнить указанную в структуре по [edi] функцию хасп-ключа
           Возврат в точку исключения + 8;

  Таким образом, функции 1,2,3,4,6 и 7 непосредственного отношения к ключу не имеют, но выполняют вспомогательные функции от простого обхода байт, вызвавших исключение (функции 1,2) до зашифрования/зашифрования блока данных, 7-ая функция вычисляет crc (байт). Однако функция 0Bh явлется одним из узловых мест защиты.

  Конечно, это не все, что делают эти функции, но это все, что необходимо эмулировать. Так, например, некоторые из них "портят" отладочные регистры DRx (DR7 только), пытаясь противостоять отладке и в первом приближении прослеживается даже попытка как-то согласовать выполнение защитного кода путем передачи через DR7 каких-то чисел но это не является обязательным; в самом крайнем случае можно сказать, что необходима лишь эмуляция функции, непосредственно использующей ключ - 0B. Однако я эмулировал все функции исключения UD: меня всегда раздражали программы, исполняющие неоптимизированный код, а каждая из этих функций предваряет свое выполнение расшифровкой (далеко не из одного цикла).

  Обработчик исключения #14 ничего особого не делает, разве что помогает расположенному в приложении коду распаковывать себя и т.п.

  Разумеется, драйвер выполняет и стандартные запросы приложения (DeviceIoControl). В этом случае он работает с ключом, и эти запросы необходимо эмулировать.

  Приложение использует построенные таким образом на исключении UD api-функции и вызовы стандартной DeviceIoControl для проверки наличия хасп-ключа. Основной особенностью использования исключения UD является попытка всеми возможными средствами маскировать вызовы функции 0B:

 - Приложение выполняет примерно 128 вызовов исключения UD, используя
   функции 1,2,3,4,6 и 7, "разбавляя" их несколькими промежуточными
   вызовами DeviceIoControl;
 - Приложение расшифровывает на стек находящийся в exe-фйале код,
   примерно ~A000h байт, причем этот код исполняется со всеми правами
   0-го кольца защиты (! - чем не вирус ?);
 - Помогая себе обработчиком SEH'а, этот код, реализующий что-то типа
   виртуальной машины (реализованы команды типа MOV,CMP, виртуального
   регистра флагов (флага ZF)) выполняет довольно серьезную проверку
   модификации драйвера защиты: командой sidt получает текущую idt,
   извлекает из нее адрес обработчика исключения PageFault и проверяет
   прямым сравнением первые байты этого обработчика: они должны,
   по его мнению, состоять из примерно следующей структуры:

     ;2822: EB18           jmps       .00000283C
     ;2824: db 47,65,6E,75,69,6E,65,44,65,76,69,6C ; 'GenuineDevil'
     ;2830: db 0E,00,02,28,33,00,B4,00,00,00,40,01,00,00
     ;283C: 1E             push        ds
     ;283D: 06             push        es
      ...
   Обратите внимание на характерную строку после обходящего сигнатуру
   короткого jmp'а - у разработчиков все в порядке с юмором. Далее будет
   еще один анекдот на эту тему.
 - Если прямое сравнение (при нем он умудряется вызвать исключение #5 - BOUND)
   не показало дефектов в драйвере, то код начинает расшифровывать полезные
   данные (код дальнейшей защиты), используя тело драйвера как ключ расшифровки.
   Если установить в критичный регион драйвера в этот момент точку останова
   (int 3) или что-то типа перехватывающего jmp/call/ret, то этот полезный
   код будет расшифрован неправильно и, поскольку впоследствии управление
   будет передано на него, выполнится с ошибкой, приводящей к краху системы
   или только приложения. Расшифровка реализована без видимых дефектов
   в режиме CBC (расшифровка каждого байта зависит от расшифровки предидущих),
   что мешает вносить исправляющие коды после команд перехвата,
   однако из существования патча от С-ля следует, что эту проблему можно
   решить атакой на шифр, но детального исследования я не проводил. Могу
   сказать только, что алгоритм вряд ли является чем-то стандартным типа MD5.
   Некоторые сомнения возникают по факту слабого анализа аргумента
   первой команды jmp short, но ...
 - Все предидущее алгоритм выполняет и с обработчиком исключения UD: выясняет
   его точку входа, прямое сравнение и расшифровка телом драйвера своего кода.
 - Если же сравнение прошло успешно, выполянется единственный вызов исключения
   UD с функцией 0B:

    mov  eax,4000000Bh
    lea  edi,[esp+4]
    UD2
    db DE,AD,BE,EF,BA,BE

 - Обратите внимание на юмор разработчиков ;)
 - Так вот, этот вызов может реализовать несколько функций хасп-ключа,
   указателем на входные данные является регистр edi.
 - После вызова функции 0B опять следует блок из ~128 вызовов "обычных"
   api исключения UD, за которым код, размещенный на стеке, опять
   проверит искажение кода драйвера и вызовет функцию 0B.
 - Всего таких блоков вызовов исключения UD, оканчивающихся вызовом с
   подфункцией 0B, будет 17 штук. В каждом из них будет проверен драйвер
   и будет выполнена попытка исполнить различные функции хасп-ключа.

  Api-Функции исключения UD вызывает не только приложение, но и сам драйвер при исполнении некоторых запросов DeviceIoControl, однако эти вызовы используют только функции 6 и 4.

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

Перехват исключения UD

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

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

  Напомню, приложение вызвает исключение UD "блоками" по ~128 штук, причем последний вызов в блоке предваряется проверкой перехвата. Следовательно, при вызове первых ~127-ми исключений можно выполнять перехват и спокойно эмулировать все функции (хотя это будут вызовы функций 1-7, необязательные к эмуляции). Однако далее необходимо будет снять перехват и взвести ожидание какого-то события, после которого можно будет восстановить перехват, но еще не вызвалось исключение UD с последней в текущем блоке подфункцией 0B (являющейся обязательной к эмуляции).

  Что может быть выбрано в качестве такого события ? Замечаем, что код на стеке, выполняющий сравнение, расшифровывает некий декриптор, еще лежащий в "обычной" области - сегменте кода приложения. Определенно до его работы код проверки еще не запущен (как, впрочем, и сразу после выполнения) и не успел сделать "черной работы". Это первая реперная точка.

  Далее управление перейдет к коду на стеке, который выполнит проверку целостности драйвера. Но вместе с этим кодом также раскриптован и код вызова исключения UD с подфункцией 0B. При помощи Soft-Ice убеждаем себя в том, что после выполнения декриптора кода на стек можно смело ставить bpx в точку вызова исключения. Это вторая реперная точка.

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

  Никаких проблем по врезке такого кода после декриптора нет, это достаточно простой цикл, густо сдобренный командами, характерными для полиморфных вирусов:

@@Loop: ... mov dl,[ebp] ; ebp указвает на зашифрованные байты mov al,al sub ebp,-1 cld mov bl,bl not dl mov ah,ah ... sub dl,cl ... xor ecx,8DFE10ECh ... ror ecx,5Eh ... add eax,-1 ... push ebp pop ebp mov [eax],dl ; eax указывает на стек, куда расшифровываем xchg ah,ah dec ebx jnz @@Loop

  Выкидываем все "нужные" команды, освобождая тем самым место для необходимого нам вызова. Вызов необходимо сделать либо в наш драйвер-перехватчик, либо в дополнительный код в виде win-32 приложения. Как наиболее простой вариант я выбрал первый и сделал это следующим образом: в этот момент еще активен перехват UD и я добавил к списку его api еще одну с кодом eax=40666666h. После декриптора вызовется наш обработчик исключения UD, уберет временно себя из цепочки обработчиков UD и установит вызов подпрограммы-восстановителя прямо перед критичным вызовом исключения UD с функцией 0B.

  Остается решить последний вопрос: когда можно будет слегда изменить этот декриптор ? Анализ показал, что на предпоследнем вызове исключения UD это делать еще рано: кода декриптора просто нет в памяти, мы не можем связать момент его появления с вызовами DeviceIoControl (их не будет, пока драйвер не будет проверен). Однако замечаем, что работа декриптора связана с вызовами исключения Page Fault:

@@Loop: ... mov dl,[ebp] ; <<< эта команда может вызвать Page Fault ! mov al,al sub ebp,-1

  Мы можем перехватить также и PageFault и на нем ожидать появление декриптора. Это произойдет не при первом вызове, но при первой дешифровке это будет обязательно. Напоследок небольшая задача: откуда взять адрес кода, выполянющего вызов исключения с функцией 0B ? (тот самый, что с Dead beef) Допустим, мы его можем сканировать в памяти и легко найти, но вот в какой памяти ? Конечно, адреса в win довольно-таки одинаковы, и можно было бы взять за границу (верхнюю) что-то типа 00690000h, но как-то криво. Однако. Где лежит SEH в момент работы расшифровщика ? Да там же, в стеке - рядом с "Dead beef". Мы смело можем извлечь базу для сканирования из FS:[0] в момент отработки уже нашего дешифровщика. Перехват же Page Fault нужен только до того момента, покуда не найден декриптор: его необходимо будет восстановить - его перехват также будет обнаружен контролирующим драйвер кодом.

  В общем и целом алгоритм перехвата UD выглядит следующим образом:

... Перехват 125-го вызова UD; | Перехват 126-го вызова UD; | Перехват 127-го вызова UD; | Перехват PageFault, ожидаем появление декриптора кода на стек; ... Перехват PageFault, ожидаем появление декриптора кода на стек; | Если декриптор появился, меняем его: (не забыв восстановить перехват Page Fault) @@NewStackDecryptor2: mov dl,[ebp] sub ebp,-1 cld not dl sub dl,cl xor ecx,8DFE10ECh ror ecx,5Eh dec eax mov [eax],dl dec ebx jnz @@NewStackDecryptor2 ; Вызов обработчика #6 push eax mov eax,SelfCallUD6 ; 40666666h ----- db 0FEh,0EFh,00h,01h,02h,03h,04h,05h | pop eax | @@EndNewStackDecryptor2: | Перехват 128-го вызова UD, но уже "наш", с функцией 40666666h; Убираем внутри перехватчика UD перехват UD; Устанавливаем в код вызова исключения UD вызов своего кода: --- mov eax,4000000Bh << push Our_Hook_Proc, ret | lea edi,[esp+4] | UD2 <----------------------------- | | ... Здесь выполняется проверка целостности драйвера | | | Our_Hook_Proc | Восстанавливаем перехватчик UD; | Передаем управление на команду UD --

  После того, как будет успешно пройден первый блок и вызов UD с функцией 0B будет перехвачен, последует следующий блок вызовов UD. Но в дальнейшем нет нужды перехватывать PageFault и ждать появления декриптора: его больше никто не будет переписывать, его будут только вызвать ! А, раз мы его уже дополнили своим вызовом, то этот вызов будет сделан и в следующих блоках. Нужно будет лишь опять устанавливать перехват вызова UD и только.

Перехват DeviceIoControl

  Не вызывает никаких проблем. Может быть реализован (на уровне VxD) несколькими способами: перехват Directed_Sys_Control (вызов конкретного драйвера из системной машины с сообщением DeviceIoControl) или же врезкой jmp (даже jz в моем варианте) в тело control-процедуры. Возможно, есть и другие варианты, но в любом случае вызваемый код никак не контролируется на предмет перехвата.

Эмуляция функций хасп-ключа

  Остается рассмотреть протокол обмена данными приложением через функцию 0B исключения UD и DeviceIoControl. В обоих случаях фактически при каждом вызове выполняется та или иная функция хасп-ключа.

  Какие же функции ключа используются ? Еще в статье exefoliator'а упоминаются около 32h функций. Тем не менее используются следующие:

Исключение UD, функция 0B выполяет функции 02,03,06 и 32h; Вызовы DeviceIoControl реализуют вызовы функций 03 и 05.

  Что из себя представляют эти функции ? В общем случае, конечно, они все что-то возвращают в зависимости от возможно необязательных аргументов. Например, функция 02: получить 64-ти битный seed по входному 16-ти битному аргументу. (Конечно, для получения верных результатов необходимо предъявить хасп-ключу уникальные ключи владельца данной линии hasp)

  В статье exefoliator'а можно было найти описание эмулятора функции 02:

4. Общий вид эмулятора Hasp ключа 80 FF 01 cmp bh,1 ; вызвали ISHASP? 75 05 jne @CheckTwo ; если нет 31 C0 xor eax,eax ; иначе 40 inc eax ; ...возвращаем в eax = 1... EB 67 jmp @EndHProc ; и выходим @CheckTwo: 80 FF 02 cmp bh,2 ; вызвали HASPCODE? 74 0A je @MakeCode ; если да EB 60 jmp @EndHProc ; иначе ничего не делать @HASPData: db x0 x1 x2 x3 x4 x5 x6 x7 ; статические данные для HASP @MakeCode: 31 C9 xor ecx,ecx ; 31 DB xor ebx,ebx ; E8 00 00 00 00 call @NextInst ; @NextInst: 5E pop esi ; @NextHBit: 53 push ebx ; сохранить число бай 66 BB 89 19 mov bx,1989h F7 E3 mul eax,ebx 83 C0 05 add eax,5 ; seed <- seed*1989h+5 0F B7 C0 movzx eax,ax ; seed <- seed and FFFFh 50 push eax ; сохранить seed C1 E8 09 shr eax,9 80 E0 3F and al,3Fh ; al <- bit offset 89 C3 mov ebx,eax C1 EB 03 shr ebx,3 ; ebx <- номер байта. 80 E0 07 and al,7 ; al <- номер бита. 51 push ecx ; сохранить текущий код 88 C1 mov cl,al B0 01 mov al,1 D2 E0 shl al,cl ; al <- битовая маска 84 44 33 EF test byte ptr [ebx+esi-11h],al ; бит установлен? B0 00 mov al,0 ; предположим что нет. 74 01 je @BitClear ; jump если ок 40 inc eax ; иначе выставляем бит @BitClear: 59 pop ecx ; ecx <- строка кода D0 E5 shl ch,1 ; идем на следующий бит 08 C5 or ch,al ; добавить последний бит 58 pop eax ; eax <- seed 5B pop ebx ; ebx <- число бит 41 inc ecx ; inc число бит 80 F9 08 cmp cl,8 ; 8 битов? 75 C7 jne @NextHBit ; jump если нет F6 C3 01 test bl,1 ; 74 03 je @EvenByte ; 5A pop edx ; получаем код возврата 88 F1 mov cl,dh ; добавляем верхние 8 бит @EvenByte: 51 push ecx ; сохраняем код возврата 31 C9 xor ecx,ecx ; очищаем строку кода 43 inc ebx ; inc число байтов 80 FB 08 cmp bl,8 ; 8байтов? 75 B6 jne @NextHBit ; jump если нет 5A pop edx ; edx <- ret code 4 59 pop ecx ; ecx <- ret code 3 5B pop ebx ; ebx <- ret code 2 58 pop eax ; eax <- ret code 1 @EndHProc: C3 ret ; возврат к месту вызова.

  Центральным моментом в этой эмулирующей функции явяляются 8 байт @HASPData (статические данные для HASP). Для чего они нужны ? Используются они один раз:

  test byte ptr [ebx+esi-11h],al ; бит установлен?

  Если аккуратно разобрать код этой эмулирующей функции, то можно понять, что @HASPData - это просто битовая таблица подстановки: в зависимости от запрошенного бита возвращает его состояние: в данном случае в регистре ebx и регистре al определено, состояние какого бита требуется. Все остальное это просто обвязка, которую, кстати, можно найти в защите непосредственно. Сравните вышеизложенное и код драйвера:

; Переменные в структуре по [ebp+xx] xor si,si xor cx,cx Loop: mov bx,1989h mov ax,[ebp+275Dh] ; <<- entry word (16 bits) mul bx add ax,5 mov [ebp+275Dh],ax ; <<- update entry word mov [ebp+276Dh],ah xor ah,ah call x014C ; Вывод в порт 0378h байта из [ebp+276Dh] call x0115 ; Чтение порта 0379h (байт) ; Бит данных в AL shl ch,1 or ch,al inc cl cmp cl,8 jnz Loop and esi,0FFFFh mov [ebp+esi+278Ch],ch xor ch,ch inc si cmp si,8 jnz Loop ; В результате в [ebp+278Ch] содержится 8 байтов seed

  Чтобы самостоятельно вычислить биты @HASPData, требуется: предъявить два 16-ти битных ключа хасп-ключу и прочесть вышеуказанным способом несколько 64-х битных seed'ов. В процессе получения этих seed'ов будут определены некоторые биты из @HASPData, эти биты следует запомнить и повторить процесс до тех пор, пока не будут определены все 64-е бита. Вот как можно предъявить ключи:

Да охранит нас 道 от 惡! - прим. wasm.ru

{$G+} const base = $0378; PortData = 0; PortRead = 1; PortServ = 2; ... procedure OutPort(PortAddr:word; PortData: Byte); begin Port[base+PortAddr]:=PortData; if Debug = true then PrintByte(PortData,'Bout='); end; function InPort(PortAddr:word):Byte; begin InPort:=Port[base+PortAddr]; end; Procedure Send_Twice(PData: Byte); begin OutPort(PortData,(PData and $FE) or $80); OutPort(PortData,(PData or $01) or $80); OutPort(PortData,(PData and $FE) or $80); end; Procedure Send_Data(var PData: array of byte); var i: word; begin i:=0; while PData[i] <> $01 do begin Send_Twice(PData[i]); inc(i); end; end; Function Read_DataBit: byte; begin Read_DataBit:= ( (InPort(PortRead) shr 5) and 1 ); end; Procedure GetKeyExtension(Key1,Key2: word; var KeyExt: array of byte); const TranslateTable: array[0..14] of byte = ($1E,$2E,$4E,$36, $56,$66,$3A,$5A, $6A,$72,$3C,$5C, $6C,$74,$78); TransTable26EA: array[0..14] of byte = ($0A,$78,$5A,$3A, $14,$48,$28,$00, $12,$50,$30,$0C, $1E,$5C,$3C); var TempKey: word; i,j: word; ResByte,TempByte: byte; begin FillChar(KeyExt,15,$FF); TempKey:=Key1 xor $1989; i:=0; j:=$0F; while i<8 do begin KeyExt[i]:=TempKey and $0F; if j<>$0F then KeyExt[j]:=(KeyExt[i]+$07) and $0F; dec(j); TempKey:=TempKey shr 4; inc(i); if i=4 then TempKey:=Key2 xor $0108; end; for i:=0 to 14 do begin if KeyExt[i]=0 then KeyExt[i]:=$0F; if i=$0D then KeyExt[i]:=$0B; KeyExt[i]:=TranslateTable[KeyExt[i]-1]; ResByte:=KeyExt[i] xor $7E; if i<>0 then begin TempByte:=ResByte; ResByte:=(ResByte-2) and TempByte; if i<7 then ResByte:=ResByte xor TempByte; end; KeyExt[i]:=ResByte xor TransTable26EA[i]; end; end; const Table27AA: array[0..7] of word = ($0180,$0140,$01C0,$0130, $0100,$0120,$0110,$0000); Key1: word = $576D; Key2: word = $2EAB; var Index27BF,Index27D1: word; HaspData: array[1..8,1..8] of byte; Res: array[0..7] of byte; ResByte,TempByte,ResBit,BitNum,LastOutput,OutBit: byte; DebStr: string; i,j,ByteNum,seed3,GetSeed3Byte: word; TestSeed,StartTestSeed: word; AllBitsDone: boolean; TestKeyExt: array[0..15] of byte; label FindBits; begin writeln; writeln('Start !'); writeln('TEST for SEED:'); OutPort(PortData,$80); Send_Twice($C6); { Выполнить сброс хасп-ключа } OutPort(PortData,$80); GetKeyExtension(Key1,Key2,TestKeyExt); { Получить расширение ключей } TestKeyExt[15]:=$01; { End of data } Send_Data(TestKeyExt); { Предъявить ключи хасп-ключу }

  Предъявив ключи, можно читать корректные значения seed. Далее в цикле получается ряд значений seed и это делается до тех пор, пока все необходимые 64-е бита не будут определены:

for i:=1 to 8 do begin for j:=1 to 8 do HaspData[i,j]:=0; end; StartTestSeed:=$6507; FindBits: TestSeed:=StartTestSeed; for i:=0 to 7 do begin for j:=0 to 7 do begin asm mov ax,TestSeed mov bx,1989h mul bx add ax,5 mov TestSeed,ax end; OutPort(PortData,((TestSeed shr 8) and $FE) or $80); ResByte:=ResByte shl 1; ResBit:=Read_DataBit; asm mov ax,TestSeed shr ax,9 and al,3Fh mov bx,ax shr bx,3 mov ByteNum,bx and al,7 mov BitNum,al mov cl,al mov al,1 shl al,cl end; {writeln('ByteNum: ',ByteNum,' BitNum:',BitNum,' Bit=',ResBit);} if ResBit = 0 then HaspData[ByteNum+1,BitNum+1]:=1 else HaspData[ByteNum+1,BitNum+1]:=2; ResByte:=ResByte or ResBit; end; Res[i]:=ResByte; end; BytesToHexWithFiller(Res,DebStr,8); writeln('SEED ?:',DebStr); AllBitsDone:=true; writeln('HASPData:'); for i:=1 to 8 do begin for j:=8 downto 1 do begin if HaspData[i,j] = 0 then begin write(' ?'); AllBitsDone:=false; end; if HaspData[i,j] = 1 then write(' 0'); if HaspData[i,j] = 2 then write(' 1'); end; writeln; end; inc(StartTestSeed); readln; if AllBitsDone=false then goto FindBits; writeln('Finish ;)'); end.

  Итак, данные @HASPData для эмулирующей функции получены и с этим мы получаем возможность эмуляции функции 02 хасп-ключа.

  Эмуляция остальных функций (03,05,06 и 32) намного проще, чем чем то, что было только что. Функция 03 читает заданное 16-ти битное слово из памяти хасп-ключа, функция 05 возвращает константу 66h - я не уверен, что это как-то зависит от содержимого ключа, функция 06 возвращает (также из памяти ключа) что-то вроде двух ID-шников ключа, функция 32h также читает память ключа. Память ключа можно получить, воспользовавшись утилитой haspgrab или же самостоятельно использовать функции 03,06 и 32 для прочтения всей памяти ключа.

Небольшое заключение

  Интересная защита для тренировки собственных навыков, достаточно простой интерфейс работы с ключом не утомляет исследователя (например по сравнению с хасп-ключом на usb).

  Спасибо всем прочитавшим :)

Полный текст эмулирующего драйвера и приложения

; ; **************** Инклуд-файл ******************** ; MAX_LOG_RECORDS equ 100 OpCodesSize equ 4 tLogPacket struc RegEAX dd ? ; Регистр EAX RegEDI dd ? ; Регистр EDI tLogPacket ends DeviceLitt_ID equ 3CDCh ; ID of little VxD NumberOfUDTraps equ 17 ; Число всех вызовов исключения UD с функцией 0Bh SelfCallUD6 equ 40666666h ; Номер собственной функции для UD6 ; ; ***************** Программа - эмулятор хасп-ключа (драйвер) ********** ; Coded by Chingachguk. 2004. ; .586p include vmm.inc include vwin32.inc DECLARE_VIRTUAL_DEVICE UD06,1,0, UD06_Control,\ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch UD06 Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl Control_Dispatch DESTROY_PROCESS, OnDESTROY_PROCESS End_control_dispatch UD06 ; Сегмент данных VxD include log06.inc VxD_LOCKED_DATA_SEG ; Буфер лога LogPtr dd 0 ; Указатель лог-буфера LogPackets db (MAX_LOG_RECORDS * sizeof(tLogPacket)) dup (?) old_ud06handler dd 0 ; Адрес обработчика UD в VxD old_14handler dd ? ; Адрес обработчика PageFault в VxD DDB dd ? ; Адрес Device Block VxD ; Переменные для перехвата Control_Proc VxD F23Addr dd 0 ; Адрес начала проверки cmp eax,23h F23JmpFound dd 0 F23EntryPoint dd ? ContinuePoint dd ? RestoreJmpArg dd ? ; Аргумент jmp - saved DirectedSysLocked dd 0 ; Флаг перехвата функций VxD pPrevHook_Directed_Sys_Control dd ? UD14EntryPoint dd 0 ; Точка входа в #14 UD6EntryPoint dd 0 ; Точка входа в #UD6 Hook14Count dd 0 ; Число вызовов исключения Page Fault HookUDCount dd 0 ; Число вызовов UD6 SaveSEHProc dd ? ; Адрес начального обработчика SEH StepNum dd ? ; Номер блока шагов по 126 SignalUnhookPortTrap dd 0 ; Флаг прекращения мониторинга i/0 CounterIOtraps dd 0 ; Счетчик обращений к портам i/0 CounterCalls14Step2 dd 0 ; Отладка - счетчик вызовов #14 на шаге 2 CrcProcHookAddr dd ? ; Адрес точки перехвата crc-процедуры AfterCrcProcHookAddr dd ? ; Адрес возврата после перехвата crc-процедуры VariantVesrion dd ? ; Вариант версии: в зависимости от него разный код StartStackDecryptor dd 0 ; Адрес начала декриптора кода на стек ; "Сигнатуры" расшифровщиков кода на стеке (2 варианта) ; Начало кода нового расшифровщика, который мы впишем вместо прежнего NewDecryptorOfs dd offset32 @@NewStackDecryptor1 dd offset32 @@NewStackDecryptor2 ; Размер расшифровщика NewDecryptorSize dd @@EndNewStackDecryptor1-@@NewStackDecryptor1 dd @@EndNewStackDecryptor2-@@NewStackDecryptor2 ; Байты - признаки завершения старого расшифровщика NewDecryptorEndBytes dw 0F68Ah dw 0CA75h ; ************** Данные эмулятора хасп-ключа ********************** HaspKey1 equ 576Dh ; Ключи: HaspKey2 equ 2EABh ; 2 word ; Память хасп-ключа: tHaspMemory struc Key1 dw ? ; Ключ 1 Key2 dw ? ; Ключ 2 MemSize dw 0080h ; Размер памяти ? KeyID1 dw ? ; Идентификатор ключа - первая часть ? KeyID2 dw ? ; Идентификатор ключа - вторая часть ? BytesFF1 db 12 dup(0FFh) Func3Data dw 24 dup(?) ; Данные для функции 3h Null1 dw 0 ; 0 BytesFF2 db 30 dup(0FFh) Func32Data dw 16 dup(?) ; Данные для функции 32h tHaspMemory ends DumpHaspMemory label byte db 06Dh,057h,0ABh,02Eh,080h,000h,050h,0E4h,07Ch,054h,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh db 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,000h,000h,000h,000h,04Dh,000h,064h,000h,000h,000h db 001h,000h,007h,000h,012h,000h,01Fh,000h,030h,000h,044h,000h,05Bh,000h,075h,000h db 092h,000h,0B3h,000h,0D6h,000h,0FDh,000h,000h,000h,020h,000h,05Fh,000h,002h,000h db 007h,000h,032h,000h,03Ch,000h,000h,000h,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh db 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh db 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0F4h,0ACh,039h,0CEh,08Dh,069h,086h,0EFh,0CAh,088h db 0ABh,009h,003h,075h,0D8h,089h,032h,040h,09Eh,09Bh,04Ch,0DEh,056h,064h,03Ah,027h db 030h,066h,00Bh,01Eh,021h,017h ; Таблица для получения seed (используется в функции 2) HaspData db 00011000b ; 18h db 00011010b ; 1Ah db 01001001b ; 49h db 01001011b ; 4Bh db 10011000b ; 98h db 10011010b ; 9Ah db 11001001b ; C9h db 11001011b ; CBh VxD_LOCKED_DATA_ENDS ; А вот наш сегмент кода. VxD_LOCKED_CODE_SEG ; ; Сообщение DESTROY_PROCESS: деинсталлируем все перехваты ; BeginProc OnDESTROY_PROCESS ; Инсталляция ловушек уже была ? cmp dword ptr ds:DirectedSysLocked,0 jz @@ContinueHook ; Убираем перехват. ; Деинсталляция перехватчиков UD6 и 23h call Uninstall_Handlers ; Мы опять готовы приступить к перехвату. mov dword ptr ds:DirectedSysLocked,0 @@ContinueHook: xor eax,eax clc ret EndProc OnDESTROY_PROCESS ; ; Сообщение DeviceIoControl ; BeginProc OnDeviceIoControl ; Сделаем так, чтобы esi был адресом переданной нам структуры - DIOCParams. assume esi:ptr DIOCParams ; При загрузке VxD в память система позовет ее с контрольными сообщениям .if [esi].dwIoControlCode==DIOC_Open ; Контрольное сообщение ?! ; ; Установка перехватчика Directed_Sys_Control: ; ; Он ожидает сообщения от VMM для Hasp.vxd и перехватывает обработчик #06 ; ; Перехватываем функцию VMM "Directed_Sys_Control" ; *** ; INT 20 P - Microsoft Windows - 0147h "Directed_Sys_Control" ; VxD = 0001h ; 0147h "Directed_Sys_Control" mov eax,00010147h mov esi,offset32 Hooked_Directed_Sys_Control int 20h ; Call VxD dw 0090h ; 0090h hook device service dw 0001h ; ID VMM mov eax,-1 jnc @@HookDirected_Sys_ControlOK xor eax,eax @@HookDirected_Sys_ControlOK: xor eax,eax ; Надо отвечать: eax=0. ; ; Сигнал завершения работы ; .elseif [esi].dwIoControlCode==DIOC_Closehandle ; *** ; Освобождаем "Directed_Sys_Control" ; *** mov eax,00010147h mov esi,offset32 Hooked_Directed_Sys_Control int 20h ; Call VxD dw 011Ch ; 011Ch unhook device service dw 0001h ; ID VMM ; ; Запрос на выдачу буфера лога ; .elseif [esi].dwIoControlCode==1 ; Получим адрес переданной нам структуры InBuffer в регистр ebx mov ebx,[esi].lpvInBuffer mov edi,[ebx] ; указатель на буфер, который нам передал win32-код mov esi,offset32 LogPtr cld lodsd stosd imul eax,eax,sizeof(tLogPacket) mov ecx,eax test eax,eax jz @@NoLogData rep movsb @@NoLogData: xor eax,eax ; Вернем в eax флаг ошибки .endif ret EndProc OnDeviceIoControl ; ; ************************** SUBPROGRAMS *********************************** ; ; ; подпрограмма - перехватчик функции 23h - DeviceIOControl: ; ожидает сообщения от VMM для hasp.vxd и перехватывает ud6 и F23 ; BeginProc Hooked_Directed_Sys_Control, HOOK_PROC, pPrevHook_Directed_Sys_Control, LOCKED push ebp mov ebp,esp pushfd ; Remember, hooks must preserve all regs pushad ; ; Нужно ли мониторить этот вызов ? cmp word ptr [ecx+6],DeviceLitt_ID jnz @@ItNotIsOurVxD ; Инсталляция обработчиков ud06 и F23 (если возможно) cmp dword ptr ds:DirectedSysLocked,0 jz @@HookFunctions jmp @@AlreadyHooked @@HookFunctions: mov dword ptr ds:DirectedSysLocked,1 ; ; Инсталлируем перехватчик #6: ; ; ;9B4D: jmp 9B63 EB 14 ; db 'GenuineDevil' = db 47,65,6E,75,69,6E,65,44,65,76,69,6C ; db 06,00,01,03,48,24,00,00 ;9B63: 1E push ds ;9B64: 06 push es ;9B65: 50 push eax [ebp+10h] ;9B66: 51 push ecx [ebp+0Ch] ;9B67: 52 push edx [ebp+8] ;9B68: 57 push edi [ebp+4] ;9B69: 55 push ebp [ebp+0] ;9B6A: 8BEC mov ebp,esp ;9B6C: 668CD0 mov ax,ss ;9B6F: 668ED8 mov ds,ax ;9B72: 668EC0 mov es,ax ;9B75: 55 push ebp ;9B76: 9C pushfd ;9B77: E800000000 call 000009B7C ;9B7C: 5D pop ebp ;9B7D: 81ED780E0000 sub ebp,000000E78 ;9B83: 9D popfd ;9B84: 0F014D00 sidt [ebp][00] ; Второй вариант, начало #6 ;29AE: EB18 jmps .0000029C8 ;29B0: db 47,65,6E,75,69,6E,65,44,65,76,69,6C ;29BC: 06,00,02,00,94,23,00,00,00,24,00,00 ;29C8: 1E push ds << jmp Our_Trap_Program ;29C9: 06 push es ;29CA: 50 push eax ;29CB: 51 push ecx ;29CC: 52 push edx ;29CD: 57 push edi ;29CE: 55 push ebp ;29CF: 8BEC mov ebp,esp ;29D1: 668CD0 mov ax,ss ;29D4: 668ED8 mov ds,ax ;29D7: 668EC0 mov es,ax ;29DA: 55 push ebp ;29DB: 9C pushfd ;29DC: E800000000 call .0000029E1 ;29E1: 5D pop ebp ;29E2: 81EDBD0E0000 sub ebp,000000EBD ;29E8: 9D popfd ;29E9: 0F014D00 sidt [ebp][00] ; Получить адрес control'а vxd call GetLitt_DDB mov eax,dword ptr ds:DDB test eax,eax jz @@VxDNotFound mov eax,dword ptr [eax+18h] ; Ищем байты начала #06. Это вторая строка 'GenuineDevil' (первая - #14) mov ecx,0FFFFh xor ebx,ebx @@FindUDEntryPoint: cmp dword ptr ds:[eax],0756E6547h jnz @@NextByteScan cmp dword ptr ds:[eax+4],044656E69h jnz @@NextByteScan test ebx,ebx jz @@UD14EntryPointFound ; Найден обработчик UD - int 6 lea edx,[eax-2] mov dword ptr ds:UD6EntryPoint,edx jmp @@ScanForUdDone ; Найден обработчик #14 - PageFault @@UD14EntryPointFound: inc ebx lea edx,[eax-2] mov dword ptr ds:UD14EntryPoint,edx @@NextByteScan: inc eax loop @@FindUDEntryPoint jmp @@NotSetup ; Найдены адреса входа в оба обработчика @@ScanForUdDone: ; Вписать команду перехода в тело обработчика #6 call Install_Jmp_Into_UD6 ; Найти команду перехода на 23 в VxD-обработчике (control proc) ; Это две команды: ; cmp eax,23h ; 83 F8 23 ; jz @L ; 0F 84 66 FF FF FF mov eax,dword ptr ds:DDB mov eax,dword ptr [eax+18h] ; Control proc ; Найти команды перехода на инсталлятор mov ecx,0FFFFh @@FindJmp23: cmp dword ptr [eax],0F23F883h jnz @@PrevByte cmp dword ptr [eax+4],0FFFF6684h jnz @@PrevByte ; Дезактивировать старые команды перехода, ; запомнив их адрес mov dword ptr F23Addr,eax mov dword ptr F23JmpFound,1 jmp @@InstallerFound @@PrevByte: inc eax loop @@FindJmp23 jmp @@NotSetup @@InstallerFound: ; Установить перехватчик функции 23h ; Запомнить EntryPoint функции 23h mov ebx,dword ptr F23Addr add ebx,3 ; Адрес jz @@F23 lea eax,[ebx+6] ; Адрес следующей команды после jz @@F23 ; Запомнить ее mov ContinuePoint,eax add eax,[ebx+2] ; Входная точка F23 mov F23EntryPoint,eax ; Вписать в команду jz @F23 адрес нашего обработчика F23 lea eax,[ebx+6] ; Адрес следующей команды после jz @@F23 sub eax,offset32 HookControl neg eax ; Размер перехода ; Сохранить ее для восстановления mov edx,dword ptr [ebx+2] mov dword ptr [ebx+2],eax ; Переход на Trap mov dword ptr RestoreJmpArg,edx ; Счетчик вызовов int 6 mov dword ptr ds:HookUDCount,0 @@VxDNotFound: @@NotSetup: @@AlreadyHooked: @@ItNotIsOurVxD: ; Продолжение выполнения popad popfd pop ebp jmp [pPrevHook_Directed_Sys_Control] ; Chain to previous hook EndProc Hooked_Directed_Sys_Control ; ; Деинсталляция перехватчиков UD6 и 23h ; Uninstall_Handlers proc pushad cmp dword ptr F23JmpFound,0 jz @@WasNotInstalled ; Восстановить команду перехода на функцию 23h ; cmp eax,23h ; 83 F8 23 ; jz @L ; 0F 84 66 FF FF FF ; Получение адреса обработчика mov ebx,dword ptr F23Addr add ebx,3 mov eax,dword ptr RestoreJmpArg mov [ebx+2],eax @@WasNotInstalled: ; Восстановить команды в VxD call Uninstall_Jmp_Into_UD6 popad ret Uninstall_Handlers endp ; ; подпрограмма - перехватчик #6 - UD ; TrapPort proc ; Команды, которые не успел выполнить обработчик #6 ;29C8: 1E push ds << jmp Our_Trap_Program ;29C9: 06 push es ;29CA: 50 push eax ;29CB: 51 push ecx ;29CC: 52 push edx EntryEAX equ dword ptr [ebp-04h] EntryEBX equ dword ptr [ebp-10h] EntryECX equ dword ptr [ebp-08h] EntryEDX equ dword ptr [ebp-0Ch] EntryEDI equ dword ptr [ebp-20h] CrashAdr equ dword ptr [ebp+18h] ; EIP ; [ebp+18h] push ds ; [ebp+14h] push es ; [ebp+10h] push eax ; [ebp+0Ch] push ecx ; [ebp+8h] push edx ; [ebp+4h] push ebp ; [ebp+0h] mov ebp,esp pushad ; push eax ; [ebp-04h] ; push ecx ; [ebp-08h] ; push edx ; [ebp-0Ch] ; push ebx ; [ebp-10h] ; push esp ; [ebp-14h] ; push ebp ; [ebp-18h] ; push esi ; [ebp-1Ch] ; push edi ; [ebp-20h] push ds push ss pop ds ; Адрес возврата EIP mov ecx,CrashAdr ; Вызов, сигнализирующий об окончании расшифровки кода на стеке ? cmp EntryEAX,SelfCallUD6 jnz @@NotSelfCallUD6 pushad ; Restore old #06 entry point call Uninstall_Jmp_Into_UD6 ; Установить, если найдем, перехват crc-процедуры call Install_CrcHook popad jmp @@ReturnToFault @@NotSelfCallUD6: ; Запомнить байты инструкции (опционально) call Store_LogRecord cmp EntryEAX,40000007h jnz @@NotF7 ; Эмуляция первого и не только вызова функции 07 ; В первом приближении это подсчет crc из [edx] длиной ecx и запись в [edi] pushad xor eax,eax mov edi,EntryEDX mov ecx,EntryECX @@CalcCRC: add al,ds:[edi] inc edi dec ecx jnz @@CalcCRC mov edi,EntryEDI mov ds:[edi],al popad ; Возврат в код приложения jmp @@ReturnToFault @@NotF7: cmp EntryEAX,40000006h jnz @@NotF6 ; Эмуляция первого и не только вызова функции 06 ; В первом приближении это расшифровка из [edx] длиной ecx xor AAh + ror 4 pushad xor eax,eax mov edi,EntryEDX mov ecx,EntryECX @@DecryptF06: xor byte ptr ds:[ecx+edi-1],0AAh ror byte ptr ds:[ecx+edi-1],4 dec ecx jnz @@DecryptF06 popad ; Возврат в код приложения, но на адрес из edi ; Модификация указателя mov eax,EntryEDI mov CrashAdr,eax jmp @@SimpleReturnToFault @@NotF6: cmp EntryEAX,40000003h jnz @@NotF3 ; Эмуляция первого и не только вызова функции 03 ; В первом приближении это расшифровка из [edx] длиной ecx rol 4 + xor AAh pushad xor eax,eax mov edi,EntryEDX mov ecx,EntryECX @@DecryptF03: rol byte ptr ds:[edi],4 xor byte ptr ds:[edi],0AAh inc edi dec ecx jnz @@DecryptF03 popad ; Возврат в код приложения jmp @@ReturnToFault @@NotF3: cmp EntryEAX,40000001h jnz @@NotF1 ; Эмуляция первого и не только вызова функции 01 ; В первом приближении это просто возврат + 8 ; Во втором - чтение из dr7 700h, запись в dr7 0, но это не обязательно ; Возврат в код приложения jmp @@ReturnToFault @@NotF1: cmp EntryEAX,40000004h jnz @@NotF4 ; Эмуляция первого и не только вызова функции 04 ; В первом приближении это расшифровка из [edx] длиной ecx rol 4 + xor AAh pushad xor eax,eax mov edi,EntryEDX mov ecx,EntryECX @@DecryptF04: rol byte ptr ds:[edi],4 xor byte ptr ds:[edi],0AAh inc edi dec ecx jnz @@DecryptF04 popad ; Возврат в код приложения, но на адрес из edi ; Модификация указателя mov eax,EntryEDI mov CrashAdr,eax jmp @@SimpleReturnToFault @@NotF4: cmp EntryEAX,40000002h jnz @@NotF2 ; Эмуляция первого и не только вызова функции 01 ; В первом приближении это просто возврат + 8 ; Во втором - запись в dr7 700h, но это не обязательно ; Возврат в код приложения jmp @@ReturnToFault @@NotF2: cmp EntryEAX,4000000Bh jnz @@NotFB ; Эмуляция первого и не только вызова функции 0B ; В первом приближении это просто возврат + 8 ; Во втором - обнуление нескольких dword по edi (если неверные ключи) mov eax,EntryEDI ; Флаг первой проверки в 0 mov dword ptr ds:[eax+20h],0 ; Переданы верные значения Key's ? cmp dword ptr ds:[eax+18h],HaspKey1 jnz @@NotFBGenSeed cmp dword ptr ds:[eax+14h],HaspKey2 jnz @@NotFBGenSeed jmp @@ExecHaspFunc @@NotFBGenSeed: ; Ключи неверны. Обнулить 4 dword и все and dword ptr ds:[eax+08h],0FFFFh and dword ptr ds:[eax+0Ch],0FFFFh mov dword ptr ds:[eax+10h],0 mov dword ptr ds:[eax+14h],0 mov dword ptr ds:[eax+18h],0 mov dword ptr ds:[eax+1Ch],0 ; Возврат в код приложения jmp @@ReturnToFault @@ExecHaspFunc: ; Ключи верные. Проверяем номер функции. Если поддерживаем - выполняем. ; Функция 02: get 64 bit seed cmp dword ptr ds:[eax+10h],0200h jnz @@NotHaspFunc02 pushad mov edi,eax ; Сгенерировать Seed movzx eax,word ptr ds:[eax+1Ch] call HaspFunc_2 ; example: enter: 6507h -> ax=7730 bx=8400 cx=19ce dx=7be8 and dword ptr ds:[edi+08h],0FFFFh and dword ptr ds:[edi+0Ch],0FFFFh mov dword ptr ds:[edi+10h],ebx mov dword ptr ds:[edi+14h],edx mov dword ptr ds:[edi+18h],ecx mov dword ptr ds:[edi+1Ch],eax popad ; Возврат в код приложения jmp @@ReturnToFault @@NotHaspFunc02: ; Функция 03: get 16 bit seed cmp dword ptr ds:[eax+10h],0300h jnz @@NotHaspFunc03 pushad mov edi,eax and dword ptr ds:[edi+08h],0FFFFh and dword ptr ds:[edi+0Ch],0FFFFh ; seed mov dword ptr ds:[edi+10h],0 mov eax,dword ptr ds:[edi+8] mov dword ptr ds:[edi+1Ch],eax mov dword ptr ds:[edi+18h],0FFFFFFFEh cmp eax,37h ja @@ErrEntrySeedForF3 call HaspFunc_3 mov dword ptr ds:[edi+10h],eax mov dword ptr ds:[edi+18h],0 @@ErrEntrySeedForF3: popad ; Возврат в код приложения jmp @@ReturnToFault @@NotHaspFunc03: ; Функция 06: get 2 word's ? cmp dword ptr ds:[eax+10h],0600h jnz @@NotHaspFunc06 pushad mov edi,eax call HaspFunc_6 and dword ptr ds:[edi+08h],0FFFFh mov dword ptr ds:[edi+10h],eax mov dword ptr ds:[edi+18h],0 mov dword ptr ds:[edi+1Ch],edx popad ; Возврат в код приложения jmp @@ReturnToFault @@NotHaspFunc06: ; Функция 32: get seed's like function 3 cmp dword ptr ds:[eax+10h],3200h jnz @@NotHaspFunc32 pushad mov edi,eax mov dword ptr ds:[edi+18h],0 ; seed - проверки ; start seed mov eax,dword ptr ds:[edi+8] mov ecx,dword ptr ds:[edi+0Ch] add eax,ecx cmp eax,40h-08h ja @@ErrEntrySeedForF32 mov esi,dword ptr ds:[edi+1Ch] mov dword ptr ds:[edi+14h],esi mov ebx,dword ptr ds:[edi+8] mov dword ptr ds:[edi+10h],ecx mov dword ptr ds:[edi+0Ch],ecx call HaspFunc_32 mov dword ptr ds:[edi+1Ch],ebx ; last seed jmp @@SeedsForF32Done @@ErrEntrySeedForF32: mov dword ptr ds:[edi+10h],0 mov dword ptr ds:[edi+14h],0 mov dword ptr ds:[edi+1Ch],0 @@SeedsForF32Done: popad ; Возврат в код приложения jmp @@ReturnToFault @@NotHaspFunc32: jmp @@ToPrevHandler @@NotFB: @@ToPrevHandler: pop ds popad pop ebp jmp dword ptr ss:old_ud06handler ; ; Возврат в код, вызвавший исключение ; @@ReturnToFault: ; Инкримент указателя EIP add CrashAdr,8 @@SimpleReturnToFault: pop ds popad pop ebp ; Команды от оригинального обработчика pop edx pop ecx pop eax pop es pop ds ; Флаг RF: resume flag после выполнения исключения - continue or byte ptr ss:[esp+0Ah],1h iretd TrapPort endp ; ; Запомнить байты в лог-буфере ; ecx-> EIP start Store_LogRecord proc ; Счетчик вызовов int 6 mov eax,dword ptr ds:HookUDCount inc dword ptr ds:HookUDCount ; (Номер шага+1) кратен 127 ? pushad add eax,2 xor edx,edx mov ebx,127 div ebx mov dword ptr ds:StepNum,eax test edx,edx popad jz @@UnhookUD6AndWaiting jmp @@StepsDone @@UnhookUD6AndWaiting: cmp dword ptr ds:StepNum,2 jae @@HookToCrcProcDone ; Устанавливаем ловушку на #14, ожидающую кода-расшифровщика на стек ; после того, как он найдет код декриптора на стек, ; он запишет после него вызов (наш) #6 call Install_Jmp_Into_14 @@HookToCrcProcDone: @@StepsDone: ; Записываем в лог только пакеты от функции 0Bh (отладка, можно убрать) cmp EntryEAX,4000000Bh jz @@LogFuncB ret @@LogFuncB: ; Запоминаем байты и значения регистров команды mov eax,dword ptr ds:LogPtr cmp eax,MAX_LOG_RECORDS jae LogFull inc dword ptr ds:LogPtr pushad imul eax,eax,sizeof(tLogPacket) mov ebx,offset32 LogPackets add ebx,eax assume ebx: ptr tLogPacket ; Регистр eax mov eax,EntryEAX mov dword ptr [ebx].RegEAX,eax ; Регистр edi mov eax,EntryEDI mov dword ptr [ebx].RegEDI,eax assume ebx:nothing popad LogFull: ret Store_LogRecord endp ; ; Перехват вызова F 23h VxD ; HookControl proc assume esi:ptr DIOCParams ; Надо ли что-то делать ? cmp dword ptr [esi].dwIoControlCode,2 jnz @@F23FuncDone cmp dword ptr [esi].lpvInBuffer,0 jz @@F23FuncDone ; Если бы мы вызвали драйвер, он сгенерировал бы 2 исключения UD add dword ptr ds:HookUDCount,2 pushad ; lpvInBuffer mov eax,[esi].lpvInBuffer ; Расшифровать буфер по способу F=06 #6 mov ecx,28h lea esi,[eax+14h] @@DecryptInBuffer: xor byte ptr [esi],0AAh ror byte ptr ds:[esi],4 inc esi loop @@DecryptInBuffer ; Проверить ключи. Если неверные - заполнить нулями и все cmp word ptr ds:[eax+20h],HaspKey1 jnz @@BadKeysForF23 cmp word ptr ds:[eax+24h],HaspKey2 jnz @@BadKeysForF23 ; Функция 3 ? cmp byte ptr ds:[eax+16h],03 jnz @@NotFunc03 mov dword ptr ds:[eax+34h],8 mov dword ptr ds:[eax+30h],0 and dword ptr ds:[eax+28h],0FFFFh ; Получить seed / вернуть ошибку mov esi,dword ptr ds:[eax+28h] mov dword ptr ds:[eax+2Ch],0E450h cmp esi,37h ja @@FuncsDone xchg eax,esi call HaspFunc_3 mov dword ptr ds:[esi+2Ch],eax mov eax,esi jmp @@FuncsDone @@NotFunc03: ; Функция 5 ? cmp byte ptr ds:[eax+16h],05 jnz @@NotFunc05 ; Заполнить константами mov dword ptr [eax+28h],1 mov dword ptr [eax+2Ch],1 push eax call HaspFunc_5 mov ecx,eax pop eax mov dword ptr [eax+30h],ecx mov dword ptr [eax+34h],0 @@NotFunc05: @@FuncsDone: jmp @@KeysOKForF23 @@BadKeysForF23: mov dword ptr [eax+28h],0 mov dword ptr [eax+2Ch],0 mov dword ptr [eax+30h],0 mov dword ptr [eax+34h],0 @@KeysOKForF23: ; Зашифровать буфер по способу F=04 #6 mov ecx,28h lea esi,[eax+14h] @@EncryptInBuffer: rol byte ptr ds:[esi],4 xor byte ptr ds:[esi],0AAh inc esi loop @@EncryptInBuffer popad assume esi:nothing @@F23FuncDone: xor eax,eax clc ret HookControl endp ; ; Вписать jmp на наш обработчик #6 ; Install_Jmp_Into_UD6 proc pushad cli ; eax=команда короткого jmp на тело mov eax,dword ptr ds:UD6EntryPoint ; вычислить начало обработчика movzx ebx,byte ptr ds:[eax+1] lea eax,[ebx+eax+2] ; Вписать jmp mov byte ptr ds:[eax],0E9h ; jmp mov ebx,offset32 TrapPort sub ebx,eax sub ebx,5 mov dword ptr ds:[eax+1],ebx ; offset add eax,5 mov dword ptr old_ud06handler,eax sti popad ret Install_Jmp_Into_UD6 endp ; ; Убрать jmp на наш обработчик #6 ; Uninstall_Jmp_Into_UD6 proc pushad cli mov eax,dword ptr old_ud06handler test eax,eax jz @@HandlerUDWasNotFound sub eax,5 ;29C8: 1E push ds << jmp Our_Trap_Program ;29C9: 06 push es ;29CA: 50 push eax ;29CB: 51 push ecx ;29CC: 52 push edx mov byte ptr ds:[eax],01Eh mov dword ptr ds:[eax+1],52515006h @@HandlerUDWasNotFound: sti popad ret Uninstall_Jmp_Into_UD6 endp Install_Jmp_Into_14 proc ; ; Вписать команду перехода на перехватчик #14 ; ;9A02: EB14 jmps .000009A18 ;9A04: db 'GenuineDevil' = db 47,65,6E,75,69,6E,65,44,65,76,69,6C ;9A10: 0E,00,01,02,D8,00,00,00, ;9A18: 1E push ds << jmp Hook_14 ;9A19: 06 push es ;9A1A: 16 push ss ;9A1B: 1F pop ds ;9A1C: 50 push eax ;9A1D: 51 push ecx ;9A1E: 52 push edx ;9A1F: 57 push edi ; Второй вариант VxD, начало #14 ;2822: EB18 jmps .00000283C ;2824: db 47,65,6E,75,69,6E,65,44,65,76,69,6C ;2830: db 0E,00,02,28,33,00,B4,00,00,00,40,01,00,00 ;283C: 1E push ds ;283D: 06 push es ;283E: 16 push ss ;283F: 1F pop ds ;2840: 50 push eax ;2841: 51 push ecx ;2842: 52 push edx ;2843: 57 push edi pushad mov eax,dword ptr ds:UD14EntryPoint ; вычислить начало обработчика movzx ebx,byte ptr ds:[eax+1] lea eax,[ebx+eax+2] mov byte ptr ds:[eax],0E9h ; jmp mov ebx,offset32 Hook_14 sub ebx,eax sub ebx,5 mov dword ptr ds:[eax+1],ebx ; offset add eax,5 ; Запомнить точку возврата mov dword ptr ds:old_14handler,eax ; Счетчик вызовов #14 mov dword ptr ds:Hook14Count,0 popad ret Install_Jmp_Into_14 endp ; ; Убрать jmp на наш обработчик #14 ; Uninstall_Jmp_Into_14 proc pushad mov eax,dword ptr ds:old_14handler test eax,eax jz @@HandlerPageFaultWasNotFound sub eax,5h ;283C: 1E push ds ;283D: 06 push es ;283E: 16 push ss ;283F: 1F pop ds ;2840: 50 push eax mov byte ptr ds:[eax],1Eh mov dword ptr ds:[eax+1],501F1606h @@HandlerPageFaultWasNotFound: popad ret Uninstall_Jmp_Into_14 endp ; ; Перехватчик исключения #14 ; Hook_14 proc EntryEIP_14 equ dword ptr [ebp+14h] ; Повторяем команды оригинального обработчика #14 ; EIP [ebp+14h] ; ErrCode [ebp+10h] push ds ; [ebp+0Ch] << jmp Hook_14 push es ; [ebp+8h] push ss pop ds push eax ; [ebp+4h] push ebp ; [ebp+0h] mov ebp,esp pushad ; Найти процедуру расшифровки кода на стек ; Ее начало: ; nop ; 90 ; mov cx,[ebp] ; 66 8B 4D 00 ; push esi ; 56 ; pop esi ; 5E ; add ebp,2 ; 83 C5 02 ; ; Или такое: ; mov dl,[ebp] ; 8A 55 00 ; mov al,al ; 8A C0 ; sub ebp,-1 ; 83 ED FF ; Счетчик вызовов #14 inc dword ptr ds:Hook14Count cmp dword ptr ds:Hook14Count,1 jbe @@NotStackDecryptor mov eax,EntryEIP_14 ; Вариант 1 ? mov dword ptr ds:VariantVesrion,0 cmp dword ptr ds:[eax],004D8B66h jnz @@TrySecondDecryptor cmp dword ptr ds:[eax+4],0C5835E56h jz @@DecryptorFound @@TrySecondDecryptor: ; Вариант 2 ? inc dword ptr ds:VariantVesrion cmp dword ptr ds:[eax],8A00558Ah jnz @@NotStackDecryptor cmp dword ptr ds:[eax+4],0FFED83C0h jnz @@NotStackDecryptor @@DecryptorFound: ; Запомнить точку начала декриптора кода на стеке mov dword ptr ds:StartStackDecryptor,eax ; Дезактивировать перехват #14 call Uninstall_Jmp_Into_14 ; Установить вызов прерывания #6 после декриптора call Install_CallAfterDecryptor @@NotStackDecryptor: popad pop ebp ; Возврат в VxD jmp dword ptr ss:old_14handler Hook_14 endp ; ; Установить вызов прерывания #6 после декриптора ; Install_CallAfterDecryptor proc pushad mov edi,dword ptr ds:StartStackDecryptor mov eax,dword ptr ds:VariantVesrion mov esi,dword ptr NewDecryptorOfs[eax*4] mov ecx,dword ptr NewDecryptorSize[eax*4] mov dx,word ptr NewDecryptorEndBytes[eax*2] @@WriteNewStackDec: mov al,byte ptr ds:[esi] mov byte ptr ds:[edi],al inc esi inc edi loop @@WriteNewStackDec @@PostNopsToDec: mov byte ptr ds:[edi],90h ; nop inc edi cmp word ptr ds:[edi],dx jnz @@PostNopsToDec mov word ptr ds:[edi],9090h popad ret @@NewStackDecryptor1: ; ; Первый вариант декриптора: ; mov cx,[ebp] add ebp,2 rol cx,0D1h add cx,dx xor edx,0C135C942h neg edx sub edi,2 mov [edi],cx cmp ebp,eax jnz @@NewStackDecryptor1 ; Вызов обработчика #6 push eax mov eax,SelfCallUD6 db 0FEh,0EFh,00h,01h,02h,03h,04h,05h pop eax @@EndNewStackDecryptor1: @@NewStackDecryptor2: mov dl,[ebp] sub ebp,-1 cld not dl sub dl,cl xor ecx,8DFE10ECh ror ecx,5Eh dec eax mov [eax],dl dec ebx jnz @@NewStackDecryptor2 ; Вызов обработчика #6 push eax mov eax,SelfCallUD6 db 0FEh,0EFh,00h,01h,02h,03h,04h,05h pop eax @@EndNewStackDecryptor2: Install_CallAfterDecryptor endp ; ; Установка перехвата процедуры в код crc-подпрограммы / прямо в вызов UD ? ; Install_CrcHook proc pushad ; Номер серии (по 126) вызовов int 6h cmp dword ptr ds:StepNum,2 jb @@NotBlock2InCallsUD ; Просто установить ловушку в crc-процедуру mov eax,dword ptr ds:CrcProcHookAddr jmp @@InstallHookCRCProc @@NotBlock2InCallsUD: ; Найти подпрограмму проверки, ; она должна быть "ниже" подпрограммы SEH ; Проверка на то, что в FS селектор из LDT mov ax,fs test ax,07h jz @@FSSelNotReady mov eax,fs:[0] ; ~00690000h ; Сохранить адрес точки перехвата crc-кода mov dword ptr ds:CrcProcHookAddr,eax @@InstallHookCRCProc: sub eax,10h mov ecx,10000h shr 1 @@FindCrcProc: ; ; Ищем первые команды вызова UD: ; mov eax,4000000Bh ; B8 0B 00 00 40 ; lea edi,[esp+4] ; 8D 7C 24 04 cmp dword ptr [eax],00000BB8h jnz @@NextCrcByte cmp dword ptr [eax+4],247C8D40h jnz @@NextCrcByte jmp @@InstallHookInCrcProc @@NextCrcByte: dec eax loop @@FindCrcProc jmp @@CrcProcNotFound @@InstallHookInCrcProc: mov byte ptr ds:[eax],68h ; push imm mov dword ptr ds:[eax+1],offset32 Hook_CRCControl mov byte ptr ds:[eax+5],0C3h ; retn ; Вычислить адрес возврата в crc-процедуру add eax,9 mov dword ptr ds:AfterCrcProcHookAddr,eax @@CrcProcNotFound: @@FSSelNotReady: popad ret Install_CrcHook endp ; ; Перехват процедуры подсчета crc ; Hook_CRCControl proc ; Устанавливаем вновь перехватчик #06 call Install_Jmp_Into_UD6 ; И вызываем код, который вызовет исключение с подфункцией 0B mov eax,4000000Bh lea edi,[esp+4] jmp dword ptr ss:AfterCrcProcHookAddr Hook_CRCControl endp ; ; Получение DDB адрес ; GetLitt_DDB proc pushad ; Определение DDB VxD mov eax,DeviceLitt_ID xor edi,edi VMMCall Get_DDB ; ecx= адрес DDB ? mov dword ptr ds:DDB,ecx popad ret GetLitt_DDB endp ; ******************* HASP FUNCTIONS ******************************* ; ; Эмуляция функции 2 hasp ; ; Подпрограмма генерирования Seed - часть эмулятора хасп-ключа. ; Спасибо exefoliator'у. ; AX = seed ; return: ax:bx:cx:dx - 64-bits answer HaspFunc_2 proc mov esi,offset32 HaspData xor ecx,ecx xor ebx,ebx @NextHBit: push ebx mov bx,1989h mul bx add ax,5 movzx eax,ax push eax shr eax,9 and al,3Fh mov ebx,eax shr ebx,3 and al,7 push ecx mov cl,al mov al,1 shl al,cl test byte ptr [ebx+esi],al mov al,0 jz @BitClear inc eax @BitClear: pop ecx shl ch,1 or ch,al pop eax pop ebx inc ecx cmp cl,8 jnz @NextHBit test bl,1 jz @EvenByte pop edx mov cl,dh @EvenByte: push ecx xor ecx,ecx inc ebx cmp bl,8 jnz @NextHBit pop edx pop ecx pop ebx pop eax ret HaspFunc_2 endp ; ; Эмуляция функции 3 hasp: получить по ax 16-ти битный seed ; HaspFunc_3 proc push ebx and eax,3Fh mov ebx,offset32 DumpHaspMemory assume ebx: ptr tHaspMemory movzx eax,word ptr [ebx+eax*2].Func3Data assume ebx:nothing pop ebx ret HaspFunc_3 endp ; ; Эмуляция функции 5 hasp: вернуть 66h (const ?) ; HaspFunc_5 proc mov eax,66h ret HaspFunc_5 endp ; ; Эмуляция функции 6 hasp: Вернуть dw 547Ch и dw E450h ; HaspFunc_6 proc push ebx mov ebx,offset32 DumpHaspMemory assume ebx: ptr tHaspMemory movzx eax,word ptr [ebx].KeyID1 movzx edx,word ptr [ebx].KeyID2 assume ebx:nothing pop ebx ret HaspFunc_6 endp ; ; Эмуляция функции 32 hasp: ; Получить seed'ов числом ecx в [esi] (ebx=last seed), start seed - ebx ; HaspFunc_32 proc push edx mov edx,offset32 DumpHaspMemory assume edx: ptr tHaspMemory @@GetSeedsForF32: lea eax,[ebx-28h] mov ax,word ptr [edx+eax*2].Func32Data mov word ptr [esi],ax add esi,2 inc ebx loop @@GetSeedsForF32 assume edx:nothing pop edx ret HaspFunc_32 endp VxD_LOCKED_CODE_ENDS end ; ; **************** Запускающий vxd код ******************** ; .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc ; Макрос invoke...etc include \masm32\include\kernel32.inc ; DeviceIOControl, CreateFile... etc includelib \masm32\lib\kernel32.lib ; Сама библиотека include \masm32\include\user32.inc ; MessageBox includelib \masm32\lib\user32.lib ; Сама библиотека .data ; Отладочные сообщения, выводимые через MessageBox MsgCaption db "Iczelion's tutorial no.2",0 ; Сообщения о процессе загрузки/выгрузки VxD AppName db "Hasp key emulator for win9x",0 Success db "The emulator is successfully loaded!",0 SerNumErr db "VxD not present or internal error...",0 Failure db "The VxD is not loaded !",0 ; Имя загружаемого VxD, которое передается CreateFile-у VxDName db "\\.\UD06.VXD",0 ; Структура, которой передаются параметры коду VxD InBuffer dd offset LogPtr ; Указатель на буфер для чтения лога include log06.inc ; Буфер лога LogPtr dd 0 ; Указатель лог-буфера LogPackets db (MAX_LOG_RECORDS * sizeof(tLogPacket)) dup (?) ; Сегмент данных программы(неинициализированных) .data? hVxD dd ? ; Тут будет храниться хэндл открытого VxD ; Начало выполнимого кода .code start: ; *** ; Открываем лог ; *** .data LogFileName db "trap_06.log",0 .data? LogDescr dd ? bWrite dd ? .code ; Открыть лог, если есть он уже invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE+FILE_SHARE_READ,\ 0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 mov LogDescr,eax ; Save file descriptor inc eax jnz @@LogFileOpen ; Видимо, нет. Тогда попробовать создать invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE+FILE_SHARE_READ,\ 0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0 mov LogDescr,eax ; Save file descriptor inc eax jnz @@LogFileOpen .data OpenErrorMsg db "Log file not created !",0Ah,0 .code push offset OpenErrorMsg call Write_Log jmp Exit @@LogFileOpen: ; Встать в конец лога invoke SetFilePointer,LogDescr,0,NULL,FILE_END ; Загрузим динамический VxD через CreateFile invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0 cmp eax,INVALID_HANDLE_VALUE ; VxD Успешно загружена ? ; Сохранить хэндл VxD mov hVxD,eax jnz @@LoadedOK @@VxDProblem: invoke CloseHandle,hVxD invoke MessageBox,NULL,addr Failure,NULL,MB_OK+MB_ICONERROR jmp Exit @@LoadedOK: ; Выдать сообщение о том, что VxD загружена успешно, давая возможность ; прекратить работу VxD invoke MessageBox,NULL,addr Success,addr AppName,MB_OK+MB_ICONINFORMATION ; Получить протокол работы invoke DeviceIoControl,hVxD,1,addr InBuffer,sizeof InBuffer,NULL,NULL,NULL,NULL ; Проверка на ошибку. Если ошибка, то eax = 0 test eax,eax jz @@VxDProblem ; Ошибки нет. Скинем данные в лог. call Print_SerialInfo ; Закроем VxD invoke CloseHandle,hVxD Exit: ; Завершим программу invoke ExitProcess,NULL ; Подпрограммы .data SerialInfo db "Total traps: ?????????? See log file trap_01.log...",0Dh,0Ah,0 LogInfoEnd db 'EAX=???????? EDI=????????',0Dh,0Ah,0 .data? LogSize dd ? .code Print_SerialInfo proc ; Сколько было записей в логе mov eax,dword ptr LogPtr mov ebx,1000000000 mov edi,offset SerialInfo+13 call DecChar invoke MessageBox,NULL,addr SerialInfo,addr MsgCaption,MB_OK push offset SerialInfo call Write_Log mov ecx,dword ptr LogPtr test ecx,ecx jnz @@IsLogData ret @@IsLogData: ; Размер лога mov eax,ecx imul eax,eax,sizeof(tLogPacket) add eax,4 ; Size mov dword ptr LogSize,eax mov ebx,offset LogPackets assume ebx: ptr tLogPacket @@OutLogData: pushad ; Регистр eax и изображение mov eax,dword ptr [ebx].RegEAX mov edi,offset LogInfoEnd+4 call HexChar ; Регистр edi mov eax,dword ptr [ebx].RegEDI mov edi,offset LogInfoEnd+17 call HexChar push offset LogInfoEnd call Write_Log popad add ebx,sizeof(tLogPacket) dec ecx jnz @@OutLogData assume ebx:nothing ret Print_SerialInfo endp ; ************* DecChar: Подпрограмма форматирования строки из числа *********** ; eax=number to digit, edi=offset result string in format 00000000(@n[ebx]) ; ebx=начальный делитель DecChar proc pushad pushfd cld @GetDec: xor edx,edx div ebx add al,'0' stosb push edx mov eax,ebx xor edx,edx mov ebx,10 div ebx mov ebx,eax pop eax test ebx,ebx jnz @GetDec popfd popad ret DecChar endp ; *** ; Подпрограмма записи в лог и на консоль одновременно ; *** ; [ebp+8] - адрес строки для записи Write_Log proc push ebp mov ebp,esp ; Получить длину строки mov esi,dword ptr [ebp+8] ; Адрес строки call Str_Len test ecx,ecx jz @@ExitWrite_Log ; Запись в протокол push NULL push offset bWrite push ecx ; Длина строки push dword ptr [ebp+8] ; Адрес строки push LogDescr call WriteFile @@ExitWrite_Log: pop ebp ret 4 Write_Log endp Write_EndStr_ToLog proc .data EndStr db 0Ah,0 .code push offset EndStr call Write_Log ret Write_EndStr_ToLog endp ; *** ; Get lenght of string ; esi=addr of string; result: ecx=length ; *** Str_Len proc xor ecx,ecx push eax push esi @@GetStrLen: lodsb test al,al jz @@ExitStrLen inc ecx jmp @@GetStrLen @@ExitStrLen: pop esi pop eax ret Str_Len endp ; Форматировать число в EAX в строку HexChar proc pushad cld mov ecx,8 mov ebx,offset TabHex @GetHex: rol eax,4 push eax and al,0fh xlat stosb pop eax loop @GetHex popad ret HexChar endp TabHex db '0123456789abcdef' end start

2002-2013 (c) wasm.ru