Взлом компонентов Delphi

Описана работа с файлами dcu результатом которой является превращение платных компонентов в бесплатные взлом дельфи delphi компоненты dcu components Павлов А.Ю.

Этот документ описывает некоторые аспекты работы с компонентами Дельфи, а именно работу с
уже скомпилированным кодом компонента в файле 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
   );

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 - поиск в нескольких файлах и поиск с произвольными символами. В общем методика полностью аналогична.

    На последок несколько советов:
  1. Поиск нужного участка кода можно выполнить найдя текст выводимый компонентом (определив его адрес в модуле и найдя ссылку на этот код - это скорее относится к OllyDbg)
  2. может возникнуть необходимость перепрыгнуть некоторый участок кода - когда нечего изменить(так было в NativeExcel который писал в ячейку A1:A1 инфу о том что это демо). Просто надо ассемблировать следующий код(tasm)
    jmp short	cs:6 + 2   ; 6 - это выход на след инструкцию после jmp - 2 сколько байт надо перепрыгнуть
    
    команда занимает 2 байта с опкодом EBXX - где хх - сколько байт надо перепрыгнуть
  3. . Если нашли в dcu нужный участок - не торопитесь сразу менять его - таких участков может быть нескотлько - замена не того может привети к ошибкам
  4. После того как изменения сохранены - проект надо закрыть и открыть ну и естественно перекомпилять :) - тогда изменения вступят в силу.
  5. Я ни в коем случае не призываю ломать все напропалую - всетаки разработчик тоже человек :), потративший на создание некоторое время и обоснованно считающий себя в праве получить некоторой вознаграждение за свой труд. В тоже время легкость с которой можно переделать практически любой компонент подкупает :). Так что как поступать - ваше дело.
Ну и для тех кто знает ассемблер - пример небольшой програмы-крякалки, ее задача заключается как раз в пропатчивании нужных файлов(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



Опубликовал admin
11 Фев, Пятница 2005г.



Программирование для чайников.