Ни для кого не секрет, что многие хорошие компоненты, предоставляющие удобную функциональность, часто имеют один общий существенный недостаток - за них надо платить. Обычно это выражается в различных предупреждающих надписях и предложениях покупки. Здесь будут рассмотрены варианты организации подобной защиты и способы ее преодоления.
Для работы вам понадобится: отладчик(желательно 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 );
lpClassName - указатель на имя класса, например TAppBuilder lpWindowName - указатель на имя окна - обычно пустое, т.е поиск всех окон указанного класса
В случае успеха - хэндл найденного окна, иначе 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 jz 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), выполнена в виде консольного приложения. Можно посмотреть что такое файловый мэппинг если вы не в курсе:
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
;------------ мэппируем файл в адресное пространство нашего процесса 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