Думаю многих интересует, как ломаются программы, как обходят регистрацию и продлевают демо-период? Есть направления, которыми хочу поделиться с Вами! ..только одену шкуру "неблагодарного пользователя", для которого добрый-дядя написал и предоставил в пользование готовый софт, а получает взамен вот что!!! Хотя, с какой колокольни глЯнуть! Может тема подтолкнёт "доброго дядю" к совершенству!? Выберем второе..
Ломать - не строить и во взломе ничего сложного нет. Защитные механизмы намного проще, чем они кажутся. Огромное количество взломанных программ свидетельствует не о всемогуществе хакеров, а об ущербности защитных механизмов! Главное начать, а там - по ходу заточится ассемблер и освоится отладчик..
Координаты......: Win32. GUI
Задача .........: Взломать приложение
Цель ...........: Отключение защитного механизма
Оружие .........: Дизассемблер/отладчик 'W32dasm' (OllyDbg)
HEX-редактор 'HIEW'
Идентификатор 'PEiD'
API-шпион 'Kerberos'
Утилиты-unpack 'UPX-Ripper', 'CASPR'
Файловые шпионы 'RegMon', 'FileMon'
Склад оружия ...: Google
Введение.
Условно-бесплатный софт делится на две категории - триал и демо. Триал - это полнофункциональные шаровары, защищенные "испытательным сроком". Демо - это версии софта, в которых часть кода физически отсутствует и соответствующая функциональность не реализована (например, печать или сохранение). Демо-версии, как правило, не имеют ограничения срока пользования, и взломать их сложнее чем триал.
Приведу простой пример: вы написали прогу, но ничего не знаете про крэкеров, дизассемблеры, отладчики. Вы создаете окошко, в котором написано "Введите регистрационный код", затем (по нажатию "OK") идёт сравнение кода с введенным значением. На заключительном этапе, в зависимости от результатов сравнения, выдаете пользователю окошко либо "Спасибо за регистрацию!", либо "Неверный регистрационный код".
Расскажу, как легко снять такую защиту! Нам понадобится отладчик W32dasm, который дизассемблирует программый код и позволит выполнить его по-шагово. Каждая следующая инструкция в программе, выполняется по нажатию клавиши F8 в отладчике. Контролируя каждый шаг программы ничё не стОит отловить функцию проверки кода (условный переход). После проверки идёт прыжок на метку (по-другому не бывает), где мы меняем команду "JE" (условие выполнено), на "JNE" (условие НЕвылнено). Теперь, если ввести любые символы, то получим переход на нужный нам участок кода. Таким образом, добавив всего одну букву "N" между инструкцией "JE", мы ломаем программу!
Знакомство с исполняемым РЕ-файлом
Рассмотрим типичное приложение Win32 (GUI). Это набор инструкций, которые выполняются последовательно. С появлением винды программисты вздохнули свободней, т.к. в их распоряжение поступило около (!)800 готовых решений в виде виндовых API-функций. В основном эти функции хранятся в файлах USER32.dll и KERNEL32.dll.
Среда разработки программ позволяет нам вызывать эти функции посредством подключения "INCLUDE". Можно создать приложение практически на одних API-функциях (не считая логико-арифметических действий). Вот код на ассемблере (FASM), который выводит окно с надписью и кнопкой, построенный на API-функциях (команда call). Запомните его!
include 'INCLUDE\win32ax.inc' ; склад API-функций
.code ; секция кода
start: ; точка входа
push 0
push 0
push text ; мессагу в стек
push 0
call [MessageBox] ; API-вызов окна
push 0
call [ExitProcess] ; API для кнопки
.data ; секция данных
text db 'Hello, world!',0 ; мессага
.end start
Теперь, скомпилируем код FASM'ом и при помощи W32dasm диззасемблируем полученный экзешник.
Задача дизассемблера - перевести машинные инструкции на язык ассемблера! Для наглядности - привожу дамп вышеприведённого экзешника "Окно с кнопкой". (чтоб незабивать пространство, я вырезал последовательности нулей):
00000000 4D 5A 80 00 01 00 00 00 04 00 10 00 FF FF 00 00 MZЂ.........яя..
00000010 40 01 00 00 00 00 00 00 40 00 00 00 00 00 00 00 @.......@.......
00000030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ............Ђ...
00000040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ..є..ґ.Н!ё.LН!Th
00000050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno
00000060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS
00000070 6D 6F 64 65 2E 0D 0A 24 00 00 00 00 00 00 00 00 mode...$........
00000080 50 45 00 00 4C 01 03 00 F6 04 E1 4D 00 00 00 00 PE..L...ц.бM....
00000090 00 00 00 00 E0 00 0E 01 0B 01 01 43 00 00 00 00 ....а......C....
000000B0 00 00 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
000000D0 00 40 00 00 00 02 00 00 4B 30 00 00 02 00 00 00 .@......K0......
00000100 00 30 00 00 93 00 00 00 00 00 00 00 00 00 00 00 .0..“...........
00000170 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00 .........text...
00000180 19 00 00 00 00 10 00 00 00 02 00 00 00 02 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`
000001A0 2E 64 61 74 61 00 00 00 0E 00 00 00 00 20 00 00 .data........ ..
000001C0 00 00 00 00 40 00 00 C0 2E 69 64 61 74 61 00 00 ....@..А.idata..
000001D0 93 00 00 00 00 30 00 00 00 02 00 00 00 06 00 00 “....0..........
000001E0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0 ............@..А
00000200 6A 00 6A 00 68 00 20 40 00 6A 00 FF 15 7C 30 40 j.j.h. @.j.я.|0@
00000210 00 6A 00 FF 15 5E 30 40 00 00 00 00 00 00 00 00 .j.я.^0@........
000003F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000400 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 00 00 00 Hello, world!...
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000005F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000600 56 30 00 00 00 00 00 00 00 00 00 00 3C 30 00 00 V0...........0..
00000610 5E 30 00 00 74 30 00 00 00 00 00 00 00 00 00 00 ^0..t0..........
00000620 4A 30 00 00 7C 30 00 00 00 00 00 00 00 00 00 00 J0..|0..........
00000630 00 00 00 00 00 00 00 00 00 00 00 00 4B 45 52 4E ............KERN
00000640 45 4C 33 32 2E 44 4C 4C 00 00 55 53 45 52 33 32 EL32.DLL..USER32
00000650 2E 44 4C 4C 00 00 66 30 00 00 00 00 00 00 66 30 .DLL..f0......f0
00000660 00 00 00 00 00 00 00 00 45 78 69 74 50 72 6F 63 ........ExitProc
00000670 65 73 73 00 84 30 00 00 00 00 00 00 84 30 00 00 ess.„0......„0..
00000680 00 00 00 00 00 00 4D 65 73 73 61 67 65 42 6F 78 ......MessageBox
00000690 41 00 C3 00 00 00 00 00 00 00 00 00 00 00 00 00 A.Г.............
000007F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Как видим - это РЕ-файл, весит он 07FFh байт (2 Кб), имеет сигнатуру "MZ" и DOS-заглушку (This programm cannot be run in DOS mode), содержит 3 секции (.text/.data/.idata), и импортирует функции из KERNEL32.dll и USER32.dll. Секция ".idata" описывает функции импорта.
По сути, это две программы в одной (сигнатуры MZ и PE)! Подпрограмма MZ осталась в наследство от DOS и может содержать в себе дос-версию приложения. Виндовый загрузчик знает об этой особенности РЕ-файла и сразу начинает работать со второй его частью.
Отладка приложения
Теперь, когда мы провели экскурсию в РЕ-файл, можно открыть нашу прогу и в отладчике W32dasm:
Disassembly of File: FASM-window.EXE
Code Offset = 00000200, Code Size = 00000200
Data Offset = 00000400, Data Size = 00000200
Number of Objects = 0003 (dec), Imagebase = 00400000h
Object01: .text RVA: 00001000 Offset: 00000200 Size: 00000200 Flags: 60000020
Object02: .data RVA: 00002000 Offset: 00000400 Size: 00000200 Flags: C0000040
Object03: .idata RVA: 00003000 Offset: 00000600 Size: 00000200 Flags: C0000040
+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules = 2 (decimal)
Import Module 001: KERNEL32.DLL
Import Module 002: USER32.DLL
+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++
Import Module 001: KERNEL32.DLL
Addr:00003066 hint(0000) Name: ExitProcess
Import Module 002: USER32.DLL
Addr:00003084 hint(0000) Name: MessageBoxA
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (FASM-window.EXE File Offset:00001400)
//******************** Program Entry Point ******** точка входа
:00401000 6A00 push 00000000
:00401002 6A00 push 00000000
* Possible StringData Ref from Data Obj ->"Hello, world!"
:00401004 6800204000 push 00402000
:00401009 6A00 push 00000000
* Reference To: USER32.MessageBoxA, Ord:0000h
:0040100B FF157C304000 Call dword ptr [0040307C]
:00401011 6A00 push 00000000
* Reference To: KERNEL32.ExitProcess, Ord:0000h
:00401013 FF155E304000 Call dword ptr [0040305E]
:00401019 00000000000000000000 BYTE 10 DUP(0)
:00401023 00000000000000000000 BYTE 10 DUP(0)
В начале (до точки входа) идёт исчерпывающая инфа о файле, функциях экспорта и импорта с перечислением задействованных библиотек. Дальше идёт точка входа в программу, где в первом столбце указан виртуальный адрес ячейки памяти, где хранится инструкция. Второй столбец содержит машинный код, который создаёт компилятор из программного кода. Третий столбец занимает дизассемблированный программный код, который мы и будем править.
Фото на память!
При программировании под Win32 мы не имеем доступ к портам ввода/вывода и не можем вызывать прерывания (главные команды DOS), у нас есть только WIN-API, которые экспортируют системные библиотеки. Вызывая API функцию, вы просто передаёте управление точке входа в функцию. Сама функция находится где-то в памяти вашего процесса, внутри некоторой библиотеки.
Для того чтобы использовать некоторую функцию, надо сначала загрузить библиотеку в свою память. Для этого, включают нужные функции в таблицу импорта и тогда виндовый загрузчик грузит библиотеки за нас. О "kernel32.dll" и "user32.dll" можно не волноваться, т.к. эти библиотеки есть в каждом процессе. Они даже проецируются в нашу память всегда по одинаковым адресам!
Функция, использует модель вызова CALL (в листинге INVOKE) и всегда возвращает значение в регистр EAX. Обычно функции содержат до 4-х параметров. Все параметры помещаются в стек в обратной последовательности и вызываемый - очищает стек. Общий вид вызова API функции будет такой:
Push 1h ; параметр 4
Push lpCaption ; параметр 3
Push lpText ; параметр 2
Push 0 ; параметр 1
Call [MessageBox] ; вызываем функцию
Push 0 ; параметр 1
call [ExitProcess] ; выход из функции
lpCaption db 'Окно регистрации',0
lpText db 'Демо-период закончится через - 30 дней!',0
В качестве первого параметра мы передаём ноль - нет родительского окна; 2-ой параметр - адрес текстовой строки; 3-ий - адрес имени окна. 4-ый - определяет стиль окна. Значения 4-го параметра приведены ниже:
0 - только кнопка ОК;
1 - ОК/Отмена;
2 - Отменить/Пропустить/Продолжить
3 - Да/Нет/Отмена
4 - Да/Нет
5 - Повторить/Отмена
6 - Отмена/Повторить/Продолжить
..в данном случае мы передали "1h", что означает две кнопки - OK и Отмена.
Мы рассмотрели функцию MessegeBox со всеми параметрами, которая представляет собой окно злополучного напоминания о регистрации. Этот код необходимо запомнить, чтоб сразу находить его в дизассемблированном листинге.
Текстовые строки
В ассемблере можно задавать только ANSI-строки, Unicode задавать сложнее и для их обработки существует целый ряд API функций. Вместо присваивания некоторого числа, можно присвоить переменную букву, но в конечном счете, эта переменная будет равна коду буквы в кодировке ANSI:
code db 'RUMIT'
Это-же можно написать символами ASCII в таком виде:
code db 52h, 55h, 4Dh, 49h, 54h
Если вы напишите эти цифры и в такой-же последовательности, то процессор обработает их как текстовую строку "RUMIT". Разработчики софта кодируют пароли именно так (в строках ASCII), но это будет слишком-уж ламерская защита.. Почесав репу они пришли к выводу, что необходимо пароли ещё и зашифровать! Методов - куча; это шифрование заменой символа предыдущим/следующим символом, "кодом Грея" и т.п. (..кстати, кодом Грея AWARD шифрует пароли биоса).
Способ замены символа является очень распространённым. Каждый символ заменяется на (+1) последующий:
RUMIT = SVNJU (+1), или
RUMIT = 53 56 4E 4A 55 (+1 ASCII)
..вот это уже похоже на то, что мы вводим как серийник! Здесь нужно учитывать, что ASCII-строки чувствительны к регистру, т.е. символ R = 52h, а символ r = 72h. Скачайте таблицу ASCII-символов, там всё видно...
Переходы и сравнения
Для переходов служит команда Jxx; для сравнения - CMP. Так-как переходы бывают условные и безусловные, поэтому я указал Jxx. Первый символ всегда J, а остальные - в зависимости от условия:
JMP - безусловная передача управления на метку
JNZ - переход, если условие не выполнено
JZ - переход, если условие выполнено
JA - переход, если значение выше нуля
JNB - переход, если значение равно нулю
Любой вызов API-функции возвращает определённое значение, и почти всегда - в регистр EAX. Для понимания материала воспользуемся всё той-же функцией "MessageBox". Вы можете спросить: что может возвращать простое окно сообщения? А возвращает оно, одно из следующих значений:
IDYES ; нажата кнопка YES - Да
IDNO ; нажата кнопка NO - Нет
IDOK ; нажата OK - ОК
IDCANCEL ; нажата кнопка CANCEL - Отмена
IDIGNORE ; нажата кнопка IGNORE - Пропустить
IDRETRY ; нажата RETRY - Повторить
Теперь, рассмотрим такой код.. Выводим окно напоминания о регистрации (NAG) с кнопками ОК и Отмена, и в зависимости от нажатой кнопки, переходим на один из двух участов кода. Здесь мы будем использовать макрос ассемблера "INVOKE". Это тоже-самое что и CALL, только формат чуть другой. Все четыре параметра функции загоняются в одну строку (в порядке очерёдности), через запятую:
invoke MessageBox,0,'текст','заголовок',MB_OK
С макросом "invoke" программа будет выглядеть следующим образом (без функций считывания/проверки кода, ..важен сам механизм):
; подключаем инклуды
include 'include\win32ax.inc'
.code
start:
; //вызываем окно с ОК/Cancel
invoke MessageBox,0,'Для продолжения необходима регистрация!','Окно регистрации',MB_OKCANCEL
; //проверяем на ОК
cmp eax,IDOK
; //если да, то прыжок на метку ОК
JZ ok ; волшебная инструкция..
; //если нет, то получай окно облома
invoke MessageBox,0,'Продолжение работы невозможно!','Окно регистрации',MB_OK
; //..после которого срабатывает катапульта
jmp exit
; //окно регистрации
ok:
invoke MessageBox,0,'Введите регистрационный код!','Окно регистрации',MB_OK
; //завершаем процесс
exit:
invoke ExitProcess,0
.end start
Смотрим на стиль окошка - MB_OK, соответственно окно выскочит с одной кнопкой OK и если мы нажмём на неё, то функция возвратит IDOK. Если нужно использовать две кнопки OK & CANCEL, то указываем MB_OKCANCEL.
Как было сказано выше, все функции возвращают значения через регистр EAX, поэтому мы проверяем его командой CMP. Чтоб соблюдать компактность в коде я не проверил кнопку CANCEL, и прыгнул сразу на окно облома, т.к. другого не дано, а код выполняется последовательно. От этого зависит конечный размер программы, который в нашем случае будет на несколько байт меньше.
Волшебную инструкцию JZ я выделил синим, т.к. она (для наших целей) имеет приоритет перед остальными. Именно от неё мы будем отталкиваться в последуюшем! Достаточно изменить её на JNZ, как мы оказываемся на функции ExitProcess.
Шпионы на службе крэкера
Посмотрим как работает API-шпион 'KERBEROS', и что покажет дизассемблер. Kerberos ведёт лог всех API-вызовов. Запустим его и натравим на нашу прогу:
kerberos API spy v1.13
(C)2004-2007 Rustem Fasihov
Tue May 31 00:20:02 2011
DLL's: 2
DLL's failed: 0
Functions: 1310
Functions failed: 0
====================================================================================
cmp.EXE | 0040104D | MessageBoxA(00000000, 0040101D, 00401007, 00000001) returns: 00000001
cmp.EXE | 004010D5 | MessageBoxA(00000000, 004010B5, 0040109F, 00000000) returns: 00000001
cmp.EXE | 004010DD | ExitProcess(00000000)
====================================================================================
END OF REPORT
..вот они вызываемые функции и их параметры в скобках. Returns - возвращаемое значение.
Дизассемблер тоже не отстаёт от шпиона и показывает своё:
//******************** Program Entry Point ********
* Reference To: USER32.MessageBoxA, Ord:0000h
:00401047 FF157C204000 Call dword ptr [0040207C]
:0040104D 83F801 cmp eax, 00000001
:00401050 7446 je 00401098
:00401052 6A00 push 00000000
:00401054 E811000000 call 0040106A
:00401059 CE into
:0040105A EAEDEE20F0E5E3 jmp E3E5:F020EEED
:00401061 E8F1F2F0E0 call E1310357
:00401066 F6E8 imul al
:00401068 E8 BYTE e8h
:00401069 00 BYTE 00h
Ага, штаб-квартира защиты найдена, и находится она по адресу :00401050. Оружие к бою!
Какие бывают защиты
В борьбе за выживание защитные механизмы обречены (хотя ничего не стоит создать защиту, которая позволит программе умереть собственной смертью). Главное - правильно забаррикадироваться! Бессмысленно вешать стальную дверь на карточный дом. Рассмотрим некоторые аспекты защиты, ..какие есть способы, методы. Их не так-уж много:
1) Метод с использованием «серийного номера» - самый простой (и дешевый) способ защиты. Обычно, в серийнике содержится следующая инфа: дата срока окончания лицензии и внутренний номер клиента.
2) Метод с использованием «ключ запроса - ключ ответа». Тут существует несколько вариантов, как правило это либо привязка к имени владельца, либо к железу компьютера. В ключе ответа (как и в предыдущем методе) содержится инфа о дате окончания лицензии и внутренний номер.
3) Для более дорогостоящего ПО используются иные способы защиты. На текущий момент достаточной популярностью для онлайн-приложений используется метод «ключ запроса – ключ ответа – веб-сервер лицензий».
Данный метод заключается в том, что ПО проверяет конфиг железа, а потом просит ввести «ключ ответа». Затем эти данные отсылаются на веб-сервер производителя, где эта инфа обрабатывается. Номер пользователя проверятся в базе данных пользователей и в зависимости от архитектуры ПО, веб-сервер может «подгружать» либо часть исполняемого кода отсутствующего в исполняемых файлах, либо часть данных необходимых для работы ПО. Наглядным примером является антивирус Касперского.
4) Метод с использованием проверки cd/dvd диска на свой/чужой. Алгоритм данного метода сводится к сбору данных о физических свойствах дисках, перевода этих данных в "ключ запроса", и получения "ключа ответа" от пользователя. Данный метод защиты является достаточно надежным и для ряда ПО просто незаменимым. Примером данного ПО могут служить: игры, базы данных, демо-версии продуктов.
5) Метод с использованием «электронного ключа». Электронный ключ представляет из себя устройство подключаемое к USB/LPT разъему. Существует множество вариаций ключей. Ключи бывают с программируемым ПЗУ, бывают без, но основная идея - использование функций, заложенных в самом ключе. То есть подавая на вход набор данных, вы получаете ответные данные. С помощью данной модели можно построить достаточно мощную систему защиты.
6) Ко всему этому, можно использовать упаковщики, которые криптуют/сжимают исполняемый файл. Крипторы - шифруют исполняемый файл. Так-же, применяются обфускаторы, которые добавляют в программный код мусор, что сильно затрудняет понимание логики программы при взломе. Обфускация возможна как исходного кода, так и исполняемых файлов. Чаще всего обфускация используется для частей программы, для которых скорость исполнения не критична. Хорошая обфускация позволяет сильно увеличить время взлома ПО.
Подробное рассмотрение всех этих методов займёт много времени, поэтому мы займёмся только взломом метода серийного номера, ограничения по времени, а так-же "отвяжем" CD/DVD-диски от игр. (..если получится)
Трепанация защитных механизмов.
Можно уметь ломать программы, не умея их защищать, а вот обратное утверждение неверно! Чтоб защитить своё творение нужно представить, как его будут ломать. Любая защита требует творческого подхода, и только работая своей головой Вы сможете создать что-либо уникальное, надежное и заслуживающее внимания.
Начнём с простого.. Думаю все встречались с играми, которые привязаны к сидюку. Устанавливаеш прогу на винт, но при запуске выходит сообщение типа: "Вставьте диск в привод!". Организовать такую защиту можно несколькими способами:
1) Проверка на наличие диска в приводе
2) Проверка на метку тома
3) Проверка на наличие свободного места на диске
4) Проверка на наличие файла и его аттрибуты
..всё это проверяется опять-же вызовами соответстующих API. Достаточно дать запрос, как получаем в регистре EAX ответ. Начнём с первого.
1) Проверка на наличие диска - функция GetDriveType. Эта Функция возвращает в регистр EAX тип диска по его имени. Cписок возвращаемых значений и сам механизм защиты выглядит так:
0 -- - Неизвестный
1 -- - Диск не найден
2 DRIVE_REMOVABLE - Гибкий
3 DRIVE_FIXED - Жесткий
4 DRIVE_REMOTE - Сетевой диск
5 DRIVE_CDROM - CD-ROM
6 DRIVE_RAMDISK - RAM диск
Для взлома - разрабатываем план! (..можно нарвать в огороде). Есть много вариантов, но мы пойдём самым коротким путём, может сработает.
Открываем игруху в W32dasm и ищем там текстовую строку "GetDriveTypeA" (Search-->Find text). Если поиск дал результаты, то где-то по-близости будет примерно следующее:
* Reference To: kernel32.GetDriveTypeA, Ord:0000h
:00401047 FF157C204000 Call dword ptr [0040207C]
:0040104D 83F801 cmp eax, 00000005
:00401050 7446 jz 00401098
Смотрим на строку CMP EAX, 00000005 и на список возвращаемых значений. EAX проверяется на пятёрку - значит программа ищет сидюк. Исправляем 5 на 3, и подсовываем программе. Теперь прога будет искать не сидюк, а жёсткий диск, что повернёт ход событий в нашу сторону. Но как изменить-то значение?
Для этого запукаем 16-тиричный редактор HIEW, открываем в нём игру и два раза жмём ENTER, что переведёт HIEW в режим дизассемблера с возможность редактирования кода. Теперь переходим по адресу проверки (в примере :0040104D), для чего жмём GOTO и указываем нужный адрес обязательно поставив точку в начале (т.е. goto-->.0040104D). Мы - на месте, ..осталось исправить инструкции.
2) Проверка на метку тома. Метка тома проверяется функцией GetVolumeInformation. Эта функция обычно вызывается вместе с "GetDriveType", что обеспечивает двойную проверку. Достаточно изменить указанную метку тома, на свою (или свою на указанную).
3) Проверка на наличие свободного места. Тоже не плохой вариант. Если диск имеет объём 700 Мб, а игра занимает на нём 605 метра, то в EAX должно находится число 95 (5Fh) и никакое другое. Проверяется функцией GetDiskFreeSpace. Обычно программисты сильно не заморачиваются и тупо проверяют этой функцией тип диска. Как правило, на CD - свободного места нет. Применяется редко и в букете с предыдущими.
4) Проверка на наличие файла. Эт метод является более продвинутым. На диске создаётся файл с защитой от копирования, который потом проверяется на присутствие. Если Вы установите игру на винт, то при установке скопируются все файлы с CD-диска, кроме файла-защиты. Функции для проверки:
GetFileAttributesA - Проверка аттрибута файла
ReadFile - Чтение файла
GetFileSize - Получение размера файла
FindFirstFile - Поиск файла.
Для снятие такой защиты достаточно запустить FileMon (файловый шпион) и игруху. Смотрим, какие файлы запрашиваются игрой, и в зависимости от применённой функции заменяем файлы на ЛЮБЫЕ, с такими-же атрибутами, типом и размером.
Вот вроде-бы и всё.. Конечно-же это не все варианты, но я Вам дал толчок, а дальше копайте сами. Есть нестандартные решения, в которых программисты выкладываются по-полной и которые ломаются не так-сходу, но общие моменты мы уже знаем.
Для перехвата API-функций хорошо выручает Kerberos. Только сначала настроим фильтр, чтобы шпион отбрасывал малоинформативные API-вызовы, захламляющие файл отчета. Откроем ke_spy.txt и закомментируем следующие функции:
TlsGetValue, DefWindowProcA, DispatchMessageA, GetFocus, GetMessageA, SendMessageA, SendMessageW, TranslateAcceleratorA, TranslateAcceleratorW и TranslateMessage.
Чтобы закомментировать функцию, перед ее именем ставится знак ';'. Для усиления фильтрации имеет смысл зайти в Опции и взвести флажок "report only .exe calls", чтобы собирать API-вызовы только EXE, а не из загружаемых им DLL.
Теперь нажимаем "Browse", указываем путь к программе, и давим "Inject". Дождавшись появления NAG'а на экране, выходим из программы и открываем файл-отчета KERBEROS'a, который находится в папке с исcледуемой программой и имеет расширение *.rep. Изучение файла-отчета лучше начинать с конца, т.к. NAG-screen появляется в последнюю очередь.
Что такое серийный номер?
Проблема защиты информации волнует людей несколько веков. Одним из самых первых шифровальных приспособлений была скитала, которая применялась ещё в V в. до н.э. Приведу пример..
Берётся цилиндр (хоть банка из-под пива), и обматывается тонкой полоской бумаги. Теперь, пишем какой-нибудь текст вдоль оси цилиндра (т.е. по-вертикали) и разматываем бумагу с цилиндра. Если эта "криптограмма" попадёт кому-нибудь в руки, то он ничё не поймёт, в отличии от того, кто додумается намотать его обратно на цилиндр, точно-такого диаметра. Секретный код "БАНКА ДЛЯ ПИВА" отправляется получателю.
Аристотелю принадлежит идея взлома такого шифра. Он предложил изготовить длинный конус и, начиная с основания обёртывать его лентой с шифрованным сообщением, постепенно сдвигая её к вершине. На каком-то участке конуса начнут просматриваться участки читаемого текста. Так определяется секретный размер цилиндра.
В настоящее время проблемами защиты информации занимается криптология. Криптология разделяется на два направления — криптографию и криптоанализ, цели которых - прямо противоположны.
Под шифрованием понимается такое преобразование информации, которое делает исходные данные нечитаемыми без знания специальной секретной информации — ключа. В результате шифрования открытый текст превращается в шифрограмму и становится нечитаемым без использования дешифрирующего преобразования.
Существует несколько классификаций шифров. По характеру использования ключа алгоритмы шифрования делятся на два типа: симметричные (с одним ключом, по другому — с секретным ключом) и несимметричные (с двумя ключами или с открытым ключом). Несимметричные алгоритмы шифрования ещё называют "асимметричными".
Не буду объяснять как работает алгоритм с секретным ключом, т.к. здесь идёт простое сравнение двух значений, а остановлюсь на асимметричных типах защит, которые поясняет рисунок:
Вы генерируете 2 ключа – секретный (он будет хранится у вас) и открытый (прошиваете его в программу, причем желательно его зашифровать). На секретном ключе будут подписываться все регистрационные ключи, которые Вы высылаете пользователям, на открытом-же ключе будет производится проверка подлинности ключа: выписан-ли ключ Вами (регистратором), или нет.
Все используемые в настоящее время "криптосистемы с открытым ключом" опираются на один из следующих типов преобразований:
1) Алгоритм RSA - разложение больших чисел на простые множители.
2) Алгоритм DH - вычисление логарифма или возведение в степень.
Таким образом, в системе кодирования по алгоритму RSA (который используется повсеместно) используются два разных ключа: один для шифрования, а второй (отличный от первого, но связанный с ним) — для дешифрования. Ключ шифрования (открытый) основан на произведении двух огромных/простых чисел, а ключ дешифрования (секретный) — на самих простых числах.
Для тех, кто рулит танком, попробую объяснить вышесказанное на примере маленькой защиты, зашифрованной по алгоритму RSA:
; Создадим/сгенерируем СЕКРЕТНЫЙ КЛЮЧ (key), для чего выбираем любые 2 числа
А = 15
B = 02
key = 15 02
; Теперь создадим ОТКРЫТЫЙ КЛЮЧ (key_0PEN), который прошьём в программу.
; Открытый ключ - это произведение значений А и В секретного ключа
key_OPEN = A * B = 30
Когда программа запросит у нас серийный номер, то мы подсовываем ей число 1502, которое она сравнивает со своим значением 30. Прога знает, что нужно перемножить эти два числа и если произведение будет равно 30-ти, то значит это не утка.
А как быть в том случае, если мы подсунем не 1502, а 1003, ..произведение-же тоже будет равно 30-ти! Всё дело в том, что прога проглотит и это значение, ..ещё и поблагодарит нас за это. Именно поэтому к одной программе может быть несколько серийников, которые можно будет разослать разным пользователям.
Как вы понимаете - это просто пример! На самом деле значения А и В секретного ключа могут содержать до (!)100 десятичных разрядов, которые потом можно разбить на группы по 4-6 цифры, перевести в 16-тиричную систему, отнять от полученного значения 5, прибавить 10 и т.д. Всё зависит от фантазии программиста.
Практикум для начинающих!
Начнём с элементарного:
1) Создадим окно, с боксом для ввода пароля и кнопками ОК/Отмена
2) Прошьём в программу пароль
3) Считаем данные из бокса, и сравним с нашим паролем
4) Выводим соответствующее сообщение (переход на метку)
Такое диалоговое окно создаётся API-функцией DialogBoxParam, которая имеет следующие параметры:
invoke DialogBoxParam, 0,69,0,ProceduraDialoga,0
Коротко поясню основные моменты:
0 - хэндл приложения;
69 - идентификатор шаблона;
0 - хэндл окна-владельца;
ProceduraDialoga - ссылка на процедуру обработки;
0 - инициализационная переменная.
Хэндл - это типа, место в иерархии. Если 0 - это родительское окно, если 1 - то окно вызвано родителем и чтоб закрыть нулевое, нужно сперва закрыть первое, а потом закроется нулевое. Как-то так..
Значит DialogBoxParam. Такое окно само нарисует на себе все необходимые элементы управления - от нас только требуется указать: какие именно, где и в каком количестве. Элементы управления будут контролами диалогового окна (их описание мы оставим в секции ресурсов). Каждый контрол диалогового окна должен иметь уникальный ID.
Как видно из рисунка, наше окно имеет 3 контрола - это поле для ввода текста Edit-box и две кнопки ОК/Отмена. Кнопки уже имеют свои ID'ы в инклудах (ID_OK, ID_CANCEL), а вот бокс нужно обозначить (ID_box=102). Число выбирается от-фонаря.
Чтобы считать текст из бокса, воспользуемся функцией GetDlgItemText. Параметры у неё такие:
invoke GetDlgItemText, [hwnddlg],ID_box,bufer,10
Коротко о главном: [hwnddlg] - хэндл диалогового окна (по-умолчанию); ID_box - от куда читать; bufer - куда читать; 10 - количество считываемых символов.
Вот мы и созрели до кода целиком, хотя одним из важных моментов является описание в секции ресурсов каждого контрола нашего окна, но это по-ходу (в комментах):
================================================================
; заголовок FASM'a
format PE GUI 4.0
; подключаем кирилицу
include 'FASM\INCLUDE\win32axp.inc'
include 'FASM\INCLUDE\encoding\WIN1251.INC'
; определяем ID бокса
ID_box = 102
; буфер в 10 байт для приёма вводимых символов. Устанавливаем пароль "12345"
.data
bufer db 11 dup 0
passw db '12345',0
; точка входа. Вызываем окно
.code
fuck:
invoke DialogBoxParam,0,69,0,ProceduraDialoga,0
; оставлю без коммента (догадайтесь сами)
mov ebx,bufer
cmp ebx,passw
jnz bad
ok:
invoke MessageBox,0,'Спасибо за регистрацию!','Окно регистрации',MB_OK
invoke ExitProcess,0
bad:
invoke MessageBox,0,'Неправильный код!','Окно регистрации',MB_OK
invoke ExitProcess,0
; обработчик окна
proc ProceduraDialoga,hwnddlg,msg,wparam,lparam
push ebx
push esi
push edi
cmp [msg],WM_INITDIALOG
je Initialization
cmp [msg],WM_COMMAND
je Command
cmp [msg],WM_CLOSE
je Close
xor eax,eax
jmp Finish
; смотрим какие кнопки юзаются
Command:
cmp [wparam],BN_CLICKED shl 16 + IDCANCEL
je Close
cmp [wparam],BN_CLICKED shl 16 + IDOK
jne Initialization
; читаем 10 символов из бокса
invoke GetDlgItemText,[hwnddlg],ID_box,bufer,10
invoke EndDialog,[hwnddlg],1
jmp Finish
Initialization:
xor eax,eax
inc eax
Finish:
pop edi
pop esi
pop ebx
ret
Close:
invoke EndDialog,[hwnddlg],-1
jmp Finish
; конец процедуры
endp
; секция ресурсов с описанием нашего окна. Цифры это координаты контролов в окне
section '.rsrc' resource data readable
directory RT_DIALOG,dialogs
resource dialogs,69,LANG_ENGLISH+SUBLANG_DEFAULT,rsrc_dialog
dialog rsrc_dialog,'Окно регистрации',70,70,190,60,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
dialogitem 'STATIC','Введите код:',-1,10,10,190,8,WS_VISIBLE
dialogitem 'EDIT','',ID_box,10,20,170,13,WS_VISIBLE+WS_BORDER+WS_TABSTOP
dialogitem 'BUTTON','ОК',IDOK,85,40,45,15,WS_VISIBLE+WS_TABSTOP+BS_DEFPUSHBUTTON
dialogitem 'BUTTON','Отмена',IDCANCEL,135,40,45,15,WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
enddialog
.end fuck
==========================================
Компилируем, запускаем, всё работает! ..но как только мы запустим свою прогу под отладчиком OllyDbg, то сразу в глаза бросается наш пароль ASCII "12345", который находится по адресу 00402018, что не есть хорошо:
00402000 > 6A 00 PUSH 0 ; +- lParam = NULL
00402002 . 68 A4204000 PUSH password.004020A4 ; | DlgProc = password.004020A4
00402007 . 6A 00 PUSH 0 ; | hOwner = NULL
00402009 . 6A 45 PUSH 45 ; | pTemplate = 45
0040200B . 6A 00 PUSH 0 ; | hInst = NULL
0040200D . FF15 88404000 CALL DWORD PTR DS:[<&USER32.DialogBoxPar>] ; +- DialogBoxParamA
00402013 . BB 00104000 MOV EBX,password.00401000
00402018 . 81FB 0A104000 CMP EBX,password.0040100A ; *** ASCII "12345" ***
0040201E . 75 45 JNZ SHORT password.00402065
00402020 . 6A 00 PUSH 0
00402022 . E8 11000000 CALL password.00402038
00402027 . CE INTO
..надо-бы как-то спрятать наш пароль от жадных взоров хакеров! Как это можно будет сделать?
Кстати, в приведённом выше листинге я проводил проверку кода по такому алгоритму:
mov ebx,bufer
cmp ebx,passw
jnz bad
..это действительно только для 3-хбайтного кода. Если пароль состоит из трёх-и-более символов, то необходимо задействовать ещё одну API-функцию - "lstrcmp", которая сравнивает текстовые строки (включая пробелы). При совпадении в EAX возвращается 1.
Увеличиваем размер "приёмного буфера", и можно писать хоть загадки (как это делает Light Alloy):
invoke lstrcmp, slovo, passw
test eax,1
jnz bad
Исследование Light Alloy
Чтоб сотворить свой механизм защиты, предлагаю рассмотреть несколько готовых вариантов и по-учиться на них. Как это делают другие, и что можно взять себе на вооружение из этих идей? Давайте разбираться.. (собирать сливки).
Я выбрал Light Alloy, как наиболее подходящий продукт для наших целей. Незарегистрированная версия проигрывателя предлагает регистрацию всякий раз, как только мы лезем в настройки. Регистрация подразумевает ответ на загадку.
Как будем действовать? План такой:
1) Устанавливаем программу на винт
2) Смотрим, упакована программа или нет
3) Диззасемблируем распакованную программу
4) Ищем в коде защиту и обходим её
Переходим к действиям! Запускаем прогу, лезем в настройки и видим такой NAG:
Теперь, запускаем диззассемблер "W32dasm" и пробуем открыть в нём экзешник "Light Alloy". Облом! Диззасемблер выдаёт ошибку и виснет. Ага, значит прога запакована и чтоб получить её код, нужно распаковать экзешник! Но как узнать, чем именно запаковали-то файл? Для этого юзаем PEiD, который покажет нам всю подноготную исполняемого PE-файла:
Так-так.. Запакована UPX-паковщиком. Придётся скачать UPX-Ripper. Распаковываем и видим, что размер экзешника стал больше (1 994 240 вместо 632 832) и диззасемблер теперь не ругается, а предоставляет нам весь исходный код. Запускаем текстовый поиск в окне "W32dasm" по первой-попавшейся загадке (Рыба в море - хвост на заборе) и попадаем сюда:
* Possible StringData Ref from Code Obj ->"ковш"
:004E6F1F B97C754E00 mov ecx, 004E757C
* Possible StringData Ref from Code Obj ->"Рыба в море - хвост на заборе."
:004E6F24 BA8C754E00 mov edx, 004E758C
:004E6F29 8B45FC mov eax, dword ptr [ebp-04]
:004E6F2C E8730C0000 call 004E7BA4
* Possible StringData Ref from Code Obj ->"овца"
:004E6F31 B9B4754E00 mov ecx, 004E75B4
* Possible StringData Ref from Code Obj ->"По горам, по долам, ходит шуба да кафтан."
:004E6F36 BAC4754E00 mov edx, 004E75C4
:004E6F3B 8B45FC mov eax, dword ptr [ebp-04]
:004E6F3E E8610C0000 call 004E7BA4
Как видим - идёт ответ, а потом вопрос. Ответ (текстовая строка "ковш") находится по адресу 004E757C и кладётся в регистр ECX. Вопрос-же - лежит по адресу 004E758C и отправляется в EDX. Далее, пересылается ввод юзера в EAX и командой CALL вызывается какая-то процедура. Адрес этой процедуры во всех загадках одинаковый - call 004E7BA4.
Нас отфутболили по адресу 004E7BA4. Идём туда и видим такой код:
* Referenced by a CALL at Addresses:
:004E7BA4 55 push ebp
:004E7BA5 8BEC mov ebp, esp
:004E7BA7 83C4EC add esp, FFFFFFEC
:004E7BAA 894DF4 mov dword ptr [ebp-0C], ecx
:004E7BAD 8955F8 mov dword ptr [ebp-08], edx
:004E7BB0 8945FC mov dword ptr [ebp-04], eax
:004E7BB3 8B45F8 mov eax, dword ptr [ebp-08]
:004E7BB6 E861D4F1FF call 0040501C
:004E7BBB 8B45F4 mov eax, dword ptr [ebp-0C]
:004E7BBE E859D4F1FF call 0040501C
:004E7BC3 33C0 xor eax, eax
..и здесь никаких условных переходов, только очередной пинок по направлению к 0040501C. Летим туда и видим долгожданную проверку!
:0040501C 85C0 test eax, eax
:0040501E 740A je 0040502A
:00405020 8B50F8 mov edx, dword ptr [eax-08]
:00405023 42 inc edx
:00405024 7E04 jle 0040502A
:00405026 F0 lock
:00405027 FF40F8 inc [eax-08]
Эта экскурсия в Light Alloy породила вывод, что защитный механизм в этом проигрывателе "размазан" по программному коду, и адрес 0040501Е не является конечным этапом проверки. Мотаем йето на ус!!!
Как и от кого защищаться!
Как уже говорилось, все приёмы давно уже отлажены и той и другой воюющей стороной. Не будем ничё изобретать, а просто ознакомимся с некоторыми из этих финтов:
1) Не давайте хакеру понять, что программа взломана! Используйте несколько уровней защиты. Первый - защита от ввода неправильного серийника, второй - защита от хакеров. Обнаружив факт взлома, первый уровень ругается явно, и хакер быстро его нейтрализует, после чего в игру вступает второй.
При грамотной реализации такой защиты, нейтрализация второго уровня потребует полного анализа всей программы. Второй уровень никогда не срабатывает у честных пользователей, а только у тех, кто купит "кряк".
2) Не показывайте хакеру, каким путем регистрируется защита. Не считываете s/n через "GetWindowText", вместо этого обрабатывайте нажатия одиночных клавиш (WM_CHAR, WM_KEYUP/WM_KEYDOWN) прямо из основного потока ввода данных, и тут же их шифруйте.
Смысл шифровки в том, чтобы вводимая пользователем строка нигде не присутствовала в памяти в явном виде. Интеграция с основным потоком ввода предотвращает быстрый взлом программы, поскольку не позволяет отличить обычные вводимые данные от s/n.
3) Возьмите на вооружение генератор случайный чисел. Пусть проверки идут с разной периодичностью с различных частей программы.
4) Ни в коем случае не храните "ругательные" строки открытым текстом и не вызывайте их по указателю. Хакер мгновенно найдет защитный код по перекрестным ссылкам. Лучше так: берем указатель на строку, увеличиваем его на N байт. Сохраняем указатель в программе, а перед использованием вычитаем N на лету.
5) Избегайте прямого вызова API-функций. Наверняка хакер поставит на них точку останова (break-point). Используйте более прогрессивные методики - копирование API-функций в свое тело, вызов не с первой команды и т.д.
6) Создайте несколько ложных функций, дав им осмысленные имена типа "CheckRegistration", ..пусть хакер тратит время на их изучение!
|