Суббота, 18.05.2024
Королевство Delphi
Главное меню
Статьи
Наш опрос
Как часто ви на этот сайт заходите?
Всего ответов: 159
Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Главная » Статьи » Защита » Разные

К вопросу о защите программ

Часть 1. Прячем формы

Как известно, многие рекомендации по совершенствованию программ, созданных с применением VCL, сводятся к простому указанию – открыть исполняемый модуль очередным Restorator’ом и поправить то или иное место в ресурсе формы или датамодуля. Наличие исходных текстов и какая-никакая документированность потоковой системы VCL привели к тому, что сегодня извлечение в читабельное представление и обратная запись содержимого ресурсов форм не является задачей, посильной только усилиям гуру.

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

1. СОЗДАНИЕ ФИЛЬТРА ЧТЕНИЯ ДАННЫХ

Итак, единственное место во всем VCL, где происходит доступ к ресурсу формы – это функция InternalReadComponentRes из Classes, текст которой приведен ниже:

function InternalReadComponentRes(
 const ResName: string;
 HInst: THandle;
 var Instance: TComponent
): Boolean;
var
 HRsrc: THandle;
begin { avoid possible EResNotFound exception }
 if HInst = 0 then HInst := HInstance;
 HRsrc := FindResource(HInst, PChar(ResName), RT_RCDATA);
 Result := HRsrc <> 0;
 if not Result then Exit;
 with TResourceStream.Create(HInst, ResName, RT_RCDATA) do
 try
 Instance := ReadComponent(Instance);
 finally
 Free;
 end;
 Result := True;
end;

Суть ее действий несложна: в модуле, определяемом параметром HInst, ищем ресурс с типом RCDATA и заданным именем. Если не находим, то возвращаем False и на этом успокаиваемся, иначе создаем поток на данных указанного ресурса и читаем из него данные методом ReadComponent.

В таком случае возникает вопрос, что нам мешает вклиниться между созданием потока и чтением из него данных с тем, чтобы перед чтением нужным образом их модифицировать? Собственно, прямых ограничений нет – нам придется лишь выполнить одну условно сомнительную операцию – модифицировать модуль Classes, дополнив его перед implementation следующим текстом:

type
 TLoadComponentFunc = function (hInst: THandle;
 const ResName: string;
 var Instance: TComponent): Boolean;

var
 LoadComponentFunc: TLoadComponentFunc;

а в implementation мы изменим функцию InternalReadComponentRes следующим образом:

function InternalReadComponentRes(
 const ResName: string;
 HInst: THandle;
 var Instance: TComponent): Boolean;
var
 HRsrc: THandle;
begin { avoid possible EResNotFound exception }
 if HInst = 0 then HInst := HInstance;
 if not Assigned(LoadComponentFunc) then
 begin
 HRsrc := FindResource(HInst, PChar(ResName), RT_RCDATA);
 Result := HRsrc <> 0;
 if not Result then Exit;
 with TResourceStream.Create(HInst, ResName, RT_RCDATA) do
 try
 Instance := ReadComponent(Instance);
 finally
 Free;
 end;
 Result := True;
 end else Result := LoadComponentFunc(HInst, ResName, Instance);
end;

Легко увидеть, что TLoadComponentFunc по описанию совпадает с InternalReadComponentRes и вызывается внутри нее, выступая в качестве того самого «клина», о котором мы и говорили выше. Описание TLoadComponentFunc и переменную, содержащую адрес обработчика мы добавляли в самом конце interface-секции Classes с единственной целью – избежать таких изменений в модуле, которые приводили бы к печально известному сообщению ("Unit xxx was compiled with another version of yyy”). Практика свидетельствует, что дописывание каких-либо новых определений в конец существующего модуля никак не влияет на «версионную отметку» вышестоящих описаний (подробнее об этом в другой раз, пока придется поверить на слово).

Таким образом, мы определили функцию-фильтр, которая будет вызываться при каждой попытке доступа к ресурсу формы. Удобной особенностью является то, что при отсутствии фильтра приложение может работать в штатном режиме, т.е. можно спокойно вести разработку и защищать данные от случая к случаю.

Пример модуля, реализующего фильтр, тождественный VCL-ному чтению:

unit DFMLoader;

interface

uses
Classes; // Classes должны быть измененными!

implementation

uses
 Windows;

function MyLoadFunc(
 HInst: THandle;
 const ResName: string;
 var Instance: TComponent
): Boolean;
begin
 with TResourceStream.Create(HInst, ResName, RT_RCDATA) do
 try
 Instance := ReadComponent(Instance);
 finally
 Free;
 end;
 Result := True;
end;

initialization
 LoadComponentFunc := @MyLoadFunc;

finalization
 LoadComponentFunc := nil;
end.

Обратим внимание на initialization и finalization. Установка фильтра помещена в initialization с тем, чтобы для активизации нашего метода защиты было достаточно просто подключить модуль к проекту. Изъятие фильтра на finalization обусловлено тем, что при использовании пакетов (packages) обращение к фильтру происходит внутри VCLXX.BPL (или RTLXX.bpl в D6), а сам фильтр располагается в другом пакете, который может быть выгружен. Именно поэтому на выходе мы и уберем за собой.

Лирическое отступление: в процессе тестирования описываемой защиты первоначально зануление фильтра отсутствовало, но быстро появилось после эффектного падения IDE на перекомпиляции модуля с защитой ;-) Кстати, IDE могло упасть только после перекомпиляции VCLXX/RTLXX, но о том, как это делалось, тоже не в этот раз.

2. РЕАЛИЗАЦИЯ ФИЛЬТРА

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

При этом обратим внимание, что начало данных ресурса формы обозначено сигнатурой "TPF0”, а после нашего преобразования там будет "UQG1” (вместо каждого символа сигнатуры мы взяли следующий за ним по таблице ASCII). Мы используем это обстоятельство для решения вопроса о том, каким образом читать ресурс.

Итак, функция чтения ресурса теперь будет выглядеть так:

function MyLoadFunc(
 HInst: THandle;
 const ResName: string;
 var Instance: TComponent
): Boolean;
const
 MySignature: array[0..3] of Char = 'UQG1';
var
 I: Integer;
 HRsrc: THandle;
 src: TResourceStream;
 Stream: TMemoryStream;
begin
 HRsrc := FindResource(HInst, PChar(ResName), RT_RCDATA);
 Result := HRsrc <> 0;
 if not Result then Exit;

 src := TResourceStream.Create(HInst, ResName, RT_RCDATA);
 try
 if LongInt(src.Memory^) = LongInt(MySignature) then
 begin
 Stream := TMemoryStream.Create;
 try
 Stream.LoadFromStream(src);
 { расшифровываем }
 for I := 0 to Stream.Size - 1 do
 Dec(Byte(PChar(Stream.Memory)[I]));
 { и загружаем }
 Instance := Stream.ReadComponent(Instance);
 finally
 Stream.Free;
 end;
 end else Instance := src.ReadComponent(Instance);
 finally
 src.Free;
 end;
 Result := True;
end;

А для того, чтобы защитить скомпилированный проект, напишем такую же простенькую «защищалку»:

program protect;

{$APPTYPE CONSOLE}

uses
 Windows, Classes;

const
 FormSignature: array[0..3] of Char = 'TPF0';

function MyEnumProc(hModule: THandle; lpResType, lpResName: PChar;
 lParam: LPARAM): BOOL; stdcall;
var
 I: Integer;
 Src: TResourceStream;
 Dst: TMemoryStream;
begin
 if DWORD(lpResName) and $FFFF0000 <> 0 then
 begin
 Src := TResourceStream.Create(hModule, lpResName, lpResType);
 try
 { удостоверимся, что это именно ресурс формы! }
 if LongInt(Src.Memory^) = LongInt(FormSignature) then
 begin
 Dst := TMemoryStream.Create;
 try
 Dst.LoadFromStream(Src);
 Dst.Position := 0;

 { зашифруем }
 for I := 0 to Dst.Size - 1 do
 Inc(Byte(PChar(Dst.Memory)[I]));

 TStrings(lParam).AddObject(lpResName, Dst);
 except
 Dst.Free;
 raise;
 end;
 
 finally
 Src.Free;
 end;
 end;
 Result := True;
end;

procedure GetResNames(const Filename: string; Items: TStrings);
var
 hModule: THandle;
begin
 hModule := LoadLibraryEx(PChar(Filename), 0, LOAD_LIBRARY_AS_DATAFILE);
 if hModule <> 0 then
 try
 EnumResourceNames(hModule, RT_RCDATA, @MyEnumProc, LPARAM(Items));
 finally
 FreeLibrary(hModule);
 end;
end;

procedure UpdateResources(const Filename: string; Items: TStrings);
var
 I: Integer;
 Stream: TMemoryStream;
 hUpdate: THandle;
begin
 hUpdate := BeginUpdateResource(PChar(Filename), False);
 if hUpdate <> 0 then
 try
 for I := 0 to Items.Count - 1 do
 begin
 Stream := Items.Objects[I] as TMemoryStream;
 UpdateResource(hUpdate, RT_RCDATA, PChar(Items[I]), 0, Stream.Memory, Stream.Size);
 end;
 finally
 EndUpdateResource(hUpdate, False);
 end;
end;

var
 I: Integer;
 Items: TStrings;
begin
 Items := TStringList.Create;
 try
 GetResNames(ParamStr(1), Items);
 UpdateResources(ParamStr(1), Items);
 finally
 { освободим временные буфера }
 for I := 0 to Items.Count - 1 do
 Items.Objects[I].Free;
 Items.Free;
 end;
end.

Что мы здесь делаем: во-первых, загружаем обрабатываемый исполняемый модуль и находим в нем все ресурсы с данными от форм (обращая внимание на то, чтобы это были именно данные форм путем проверки сигнатуры). Преобразованные копии этих ресурсов вместе с именами мы складываем в список.

Затем мы освобождаем модуль и открываем его уже для изменения ресурсов (одновременно произвести два открытия нам не дадут). Т.к. нам известны имена, типы и содержимое обновляемых ресурсов, ничто не мешает нам заменить ресурсы и, запустив программу, убедиться, что все работает. Осмотр же с использованием Restorator’а более не выявляет внутри программы каких-либо форм. Что и требовалось доказать.

3. ЧТО ОСТАЛОСЬ ЗА КАДРОМ

Во-первых, обработка большинства ошибок. Например, отсутствие на диске исходного файла в защищалке. Или более тщательная проверка целостности и корректности информации в шифрованных ресурсах. Для описания концепции приведенной информации вполне достаточно, а при развитии идеи появятся иные места для проверки.

Во-вторых, системная функция обновления ресурсов в файле есть только в WinNT/2K/XP. Однако для целей защиты всегда можно найти машину с указанными ОС или, перелопатив MSDN, написать полностью свое обновление ресурсов.

В-третьих, наше предположение о том, что все операции чтения ресурсов будут проходить именно через наш фильтр, основывается исключительно на анализе исходного кода VCL. Впрочем, кроме VCL никто данные в ресурсы форм и не пишет. Если у вас дело обстоит не так, то уделите внимание вопросу разрешения потенциальных конфликтов.

В-четвертых, диапазон возможностей, которые открываются в описанном подходе, не ограничивается только преобразованием ресурсов. В фильтре вполне возможно реализовать чтение данных через Интернет. Ну или хотя бы преобразовать их через установленный электронный ключ… Или в самом простом случае, упаковать их каким-либо алгоритмом (zLib, к примеру).

4. ЗАКЛЮЧЕНИЕ

Итак, что мы имеем в плюсе: сокрытие ресурсов от постороннего взгляда; больший контроль над процессом загрузки форм; палки, установленные в колеса декомпиляторам; прозрачность защиты – без применения защищалки приложение также будет корректно работать.

Особая изюминка заключается в сложности написания универсальной «открывашки» для ресурсов – даже если для версии N Вашей программы определили алгоритм восстановления ресурсов, в версии N+1 Вы незначительно изменяете алгоритм и делаете бесполезной предыдущий хакерский труд.

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

А что же в минусе: необходимость модифицировать исходный текст VCL и поддерживать затем эти изменения при переходе к новым версиям или установки UpdatePack’ов, но это и в самом деле не так страшно, как может показаться. Во всяком случае, за те пять лет, что применяется описанный метод, это никогда не составляло серьезной проблемы.

Автор: Евгений Каснерик

Получить ссылку на материал

Категория: Разные | Добавил: Барон (16.12.2011)
Просмотров: 1201 | Теги: защита | Рейтинг: 0.0/0
[ Пожертвования для сайта ] [ Пожаловаться на материал ]

Если вам помог материал сайта кликните по оплаченной рекламе размещенной в центре

Поиск
Категории раздела
Бомберы [0]
Трояны [0]
Робота с паролем [4]
Delphi и Хакинг [2]
Шифрование [6]
Разные [25]
Королевство Delphi © 2010-2024
Яндекс цитирования