Взлом компонентов Delphi Описана
работа с файлами dcu результатом которой является превращение платных
компонентов в бесплатные. Этот документ описывает некоторые аспекты
работы с компонентами Дельфи, а именно работу с уже скомпилированным
кодом компонента в файле dcu для внесения в него некоторых полезных
изменений.
Не для кого не секрет что многие хорошие компоненты предоставляющие
удобную функциональность часто имеют один общий существенный недостаток
- за них надо платить. Обычно это выражается в различных
предупреждающих надписях и предложениях покупки. Здесь будут
рассмотрены варианты организации подобной защиты и способы ее
преодаления. Для работы вам понадобится: отладчик(желательно SoftIce но можно обойтись и без него или хотя бы OllyDebugger - очень хороший отладчик пользовательского уровня), hex - редактор(я использую WinHex -
очень мощная программа), минимально понимание winapi и общей работы
Windows а также хотя-бы представление о языке программирования
ассемблер(не помешает какой-либо асемблер - я лично предпочитаю tasm). Итак,
приступим. Случай первый, симптомы : предупреждающее сообщение при
запуске программы в случае незапущенного IDE. Ясно что компонент при
запуске программы проверяет наличие запущенного IDE - если нет получаем
сюрприз. Самый распростроненный способ - проверка наличия в системе
окон определенного класса, которые создает среда разработки. Этот поиск
осуществляется функцией FindWindow, смотрим ее описание в Win SDK: HWND FindWindow( LPCTSTR lpClassName, // pointer to class name LPCTSTR lpWindowName // pointer to window name );
В случае успеха - хэндл найденного окна иначе 0; Методика работы: - Создаем приложение с интересующим нас компонентом - подключенным dcu.
- Запускаем SoftIce и ставим бряк на FindWindow(кто не знает bpx FindWindowA ну или FindWindowExA или смотрите exp FindWindow) - айс запустится в момент вызова этой функции.
- Запускаем программу.
- Попадаем
в айс - проматываем F10 до выхода из FindWindow и узнаем откуда была
вызвана эта функция (адрес возврата конечно можно вытащить и из стека)
Что мы видим в отладчике(компонент NativeExcel):
push 00h // 0 - пустой указатель на имя окна push 0005F6498 // указатель на класс окна call USER32!FindWindowA mov ebx,eax // сохранение результата push 00h push 005F64A8 call USER32!FindWindowA mov esi,eax push 00h push 005F64B8 call USER32!FindWindowA mov edi,eax push 00h push 5F64CC call USER32!FindWindowA test ebx,ebx // проверка результата - видно если окна нет - прыгаем куда-то jz 005E136D // как раз на сообщение test esi,esi jz 005E136D test edi,edi jz 005E136D test eax,eax z 005E136D jmp куда-то на выход // надо добраться сюдa По
адресам передаваемым в функцию FindWindow в даном случае находятся
TApplication, TAlignPalette, TPropertyInspector, TAppBuilder. Кстати
важное замечание - практически все функции WinApi возвращяют результат
в регистр eax - т.е. в нашем случае в eax будет содержаться хэндл окна
или 0. Например в Ems QuickPDF полностью аналогичный код - правда
проверок меньше - и успокаивается в случае если хотя-бы одно окно есть
в системе.
Так что с этим делать? Ответ прост - самое
правильное найти этот код в файле dcu используя hex-редактор и немного
его поправить. Если используется WinHex - просто забиваем код в шаблон
и ищем(кстати call выглядит как E800000000 - нули это адрес который
проставит PE-загрузчик при загрузке файла). Заменять
инструкцию call нельзя - так как загрузчик пропатчивая адрес вызова
снесет все что было вами туда записано - в результатеполучится
случайная инструкция обычно приводящая к ошибке памяти. Самое простое
решение в данном случае - заменить 2-х байтовую команду test на
например 2 однобайтовые команды inc - кто не знает - эта команда
увеличивает операнд на 1, вот некоторые опкоды - вы можете сами
посмотреть их создав процедуру или программу на ассемблере и посмотрев
ее в отладчике: inc eax 40h inc ebx 43h inc esi 46h inc edi 47h Кому
не лом может посмотреть правила формирования команд процессора. Итак
найдя в dcu нужный код меняем инструкцию 84С0 на 4040 в итоге получаем:
- теперь компонент думает что ide запущено несмотря ни на что.
Таким
образом разобран первый случай, переходим ко второму. Симптомы: c
первого взгляда теже - но сообщение появляется вне зависимотсти от
наличия в системе ide. В чем дело - случайно догадываемся что скорее
всего дело в отладчике, т.е. программа проверяет наличие отладчика - и
если его нет - мы имеем плачевный результат. Как это можно определить -
смотрим sdk: The IsDebuggerPresent function indicates whether the calling process is running under the context of a debugger.
BOOL IsDebuggerPresent(VOID) Эта
функция возвращает 0 если текущий процесс запущен не из под отладчика и
не 0 в противном случае. Далее технология подобна описанноы выше. Этот
способ представлен в пакете AlphaControls - большой набор очень
красивых контролов. Правда разработчики этого пакета поступили хитро
напихав проверок в разный молули (защита проявляется последовательно
при добавлении новых компонентов в проект и проверки находятся в файлах
sStyleSimple.dcu, sCommonData.dcu, sStypePassive.dcu) - тут проявилось
очень важное свойство WinHex - поиск в нескольких файлах и поиск с
произвольными символами. В общем методика полностью аналогична. На последок несколько советов: - Поиск
нужного участка кода можно выполнить найдя текст выводимый компонентом
(определив его адрес в модуле и найдя ссылку на этот код - это скорее
относится к OllyDbg)
- может возникнуть необходимость
перепрыгнуть некоторый участок кода - когда нечего изменить(так было в
NativeExcel который писал в ячейку A1:A1 инфу о том что это демо).
Просто надо ассемблировать следующий код(tasm)
jmp short cs:6 + 2 ; 6 - это выход на след инструкцию после jmp - 2 сколько байт надо перепрыгнуть команда занимает 2 байта с опкодом EBXX - где хх - сколько байт надо перепрыгнуть - .
Если нашли в dcu нужный участок - не торопитесь сразу менять его -
таких участков может быть нескотлько - замена не того может привети к
ошибкам
- После того как изменения сохранены - проект надо
закрыть и открыть ну и естественно перекомпилять :) - тогда изменения
вступят в силу.
- Я ни в коем случае не призываю ломать все
напропалую - всетаки разработчик тоже человек :), потративший на
создание некоторое время и обоснованно считающий себя в праве получить
некоторой вознаграждение за свой труд. В тоже время легкость с которой
можно переделать практически любой компонент подкупает :). Так что как
поступать - ваше дело.
Ну и для тех кто знает ассемблер -
пример небольшой програмы-крякалки, ее задача заключается как раз в
пропатчивании нужных файлов(tasm все константы взяты из файла
Windows.pas), выполнена в виде консольного приложения. Можно посмотреть
что такое файловый мэппинг если вы не в курсе:
.386
includelib import32.lib include const32.inc extern ExitProcess: proc extern GetStdHandle: proc extern WriteConsoleA: proc extern CreateFileA: proc extern CreateFileMappingA: proc extern MapViewOfFile: proc extern CloseHandle: proc extern UnmapViewOfFile: proc extern MessageBoxA: proc extern FormatMessageA: proc extern GetLastError: proc extern LocalFree: proc
.model flat .data
SHandle dd ? data db `1234343`,0Ah,0Dh,0 Result dd ? w32_f_d _WIN32_FIND_DATAA <0,0,0,0,0,0,0,0,0,0> old_str db 0C0h,084h new_str db 040h,040h m_title db `title`,0
FileHandle dd ? FileMap dd ? MemBase dd ?
FileName db `data.txt`,0 my_map_name db `my_map11`,0
buf_str dd ?
Enter db 0Ah,0Dh,00h ; file1 db `dlg1.res`,0 ; file2 db `dll.bat`,0 ; file3 db `dll.asm`,0 ; file_names dd offset file1, offset file2,offset file3 ; имена файлов которые надо патчить file_lengths dd 08,07,07 ; длины имен - для вывода на консоль file_offsets dd 00h,00h,00h ; смещения нужного кода num dd 2
.code Start: WriteC macro Text,len push 0 push offset Result push len push Text push SHandle call WriteConsoleA
push 0 push offset Result push dword ptr 2 push offset Enter push SHandle call WriteConsoleA
endm
; получаем консоль push STD_OUTPUT_HANDLE call GetStdHandle mov SHandle,eax test eax,eax jz on_error
; начанаем непосредственно крякать :)
start_crack:
mov ecx,num xor ebx,ebx push ebx push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push ebx inc ebx push ebx xor ebx,ebx push 80000000h or 40000000h push dword ptr file_names[ecx*4] call CreateFileA ; открываем файл inc eax test eax,eax jz on_error ;если ошибка - выходим dec eax mov FileHandle,eax
xor ebx,ebx ;------------ создаем карту файла push offset my_map_name push ebx push ebx push PAGE_READWRITE push ebx push eax call CreateFileMappingA test eax,eax ;если ошибка - на выход jz on_error mov FileMap,eax
xor ebx,ebx
;------------ мэппируем файл в адресное пространство нашего процесса push ebx push ebx push ebx push 00000002h push eax call MapViewOfFile test eax,eax ;если ошибка - на выход jz on_error mov MemBase,eax
mov ecx,num mov edi,eax add edi,dword ptr file_offsets[ecx] mov esi,offset old_str push edi cmpsw ; сравниваем байты по смещению с шаблоном jne on_incorrect_file ; если что-то не то - выходим
pop edi mov esi,offset new_str movsw jmp on_free_resource
on_incorrect_file:
pop edi
on_free_resource: ; освобождаем ресурсы
push MemBase call UnmapViewOfFile push FileMap call CloseHandle
push FileHandle call CloseHandle
;=== inc
mov ecx,num WriteC file_names[ecx*4],file_lengths[ecx*4]
dec num jns start_crack
jmp on_close
on_error: ; сообщение о ошибке через FormatMessage
call GetLastError push 0 push 100h push offset buf_str push 0 push eax push FORMAT_MESSAGE_FROM_HMODULE push FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM call FormatMessageA test eax,eax jz on_close push 0 push offset m_title push buf_str push 0 call MessageBoxA push buf_str call LocalFree
on_close:
push 00h call ExitProcess end Start
|