IT-Блог о программировании и операционных системах

Внедрение своей DLL в среду Delphi

Данная заметка, является логическим продолжением предыдущего материала по расширению возможностей IDE. На этот раз мы внедрим в среду свою DLL-библиотеку. Для чего это можно применить? Хороший вопрос! Если честно – я пока и сам не знаю. Но надеюсь, что по мере написания данного материала, некая шальная мысля ударит в мою голову. А пока, давайте будем расценивать данную статью просто как очередную авторскую заметку, с его мыслями и соображениями. Итак, как всё будет происходить? Мы напишем некую DLL, которая будет выполнять некие полезные (?) действия, перед стартом среды.

Итак, первым делом нам нужно написать саму библиотеку. Я долго не мог придумать, что такого полезного она будет делать и моей фантазии хватило только на то, чтобы она позволяла нам выбирать, какие мастера загружать вместе со средой. То есть, перед стартом среды, появляется некое окно, в котором мы можем выбирать, какие визарды загружать, а какие нет. Это может пригодиться например при разработке подобных мастеров, вернее во время их отладки. Например если какой либо новый визард будет пестрить ошибками во время загрузки IDE, тем самым не давая ей нормально работать – мы сможем его отключить, очень удобным образом.

Разработка DLL

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

main_form

По двойному клику на имени мастера можно сделать его активным, на момент старта среды, или просто доступным для этого, то есть неактивным. На картинке видно, что мастер CnWizard загрузится вместе со средой, а тестовый мастер SpeedUpDelphi – нет. По нажатии на кнопку “Далее” или просто при закрытии окна, будет стартовать ide. Я понимаю, что такую программу можно было написать без всякого внедрения в саму среду, просто порождая новый процесс Delphi при закрытии формы. Но как я уже говорил, у меня очень скудная фантазия (ну такой вот я ) и придумать ничего не смог. Это просто пример, так что вы можете реализовать сколько угодно сложную функцию и внедрить ее в среду как будет показано ниже. Не буду ходить вокруг да около и приведу код своей dll-библиотеки:

library IDE;
uses
SysUtils,
Classes,
Windows,
Main in 'Main.pas' {frmMain};
{$R *.res}
procedure ShowMainForm;
begin
frmMain := TfrmMain.Create(frmMain);
frmMain.ShowModal;
frmMain.Free;
end;

exports ShowMainForm;

begin
end.


Как видите, всё как обычно, особо рассказывать тут не о чем. В процедуре ShowMainForm, создаём форму, показываем ее и освобождаем. Я долго думал над тем, размещать код формы в статье или нет. С одной стороны это хорошо, а с другой займёт достаточно большой обьём места. Постить я его не стал. Архив всего проекта можно скачать в конце статьи. А сейчас, лучше, перейдём к самому интересному – к инжекту.



Внедрение



Нам необходимо, чтобы при загрузке IDE Delphi, загрузилась наша форма. Как этого достичь? Мы воспользуемся отладчиком Olly и изменим программу так, чтобы она сначала показала нашу форму, используя экспортируемую форму из DLL, а уже потом загрузилась сама. Я буду приводить пример на среде Delphi 7, однако вы можете повторить всё это и на остальных. Итак, создайте резервную копию файла delphi32.exe и загрузите ее в отладчик:



olly1



Синей рамкой я отметил начало участка, в который мы запишем свой код динамической загрузки dll-библиотеки. То есть, отсюда и начнётся выполнение программы после окончательного патча. Как работать с Olly я вкратце рассказывал в своих предыдущих постах. Для того чтобы передать управление на наш код, нам необходимо найти место, где мы напишем этот самый код (я его отметил), написать код и передать на него управление с Entry Point (самая первая инструкция на скриншоте) инструкцией jmp <адрес>, где адрес – адрес начала нашего кода. Для своего кода я выбрал адрес 41FEF4. Взгляните на следующий скриншот:



olly2



Итак, по порядку. Выполнение программы начинается с адреса 0041FE7C (первая строка кода). Мы записали туда инструкцию JMP SHORT 41FEF4, тем самым заставили программу выполняться с 0041FEF4. Схематично этот переход изображает красная непрерывная стрелка. Красная пунктирная стрелка в свою очередь демонстрирует, как будет выполняться наш (вставленный в тело программы) код. Весь код, который нужно написать выделен в зелёный прямоугольник (его рассмотрим позднее). После того как наш код отработает (загрузит нашу DLL и выполнит процедуру ShowMainForm), инструкция JMP 0041FE7F передаст управление на этот адрес. Это показывает синий указатель. Теперь рассмотрим сам код:



olly3





Я прокомментировал каждую строчку нашего кода, так что вроде бы никаких затруднений быть не должно. Может встать вопрос о том, как записать в Olly строки, адрес которых мы передаём функциям LoadLibrary и GetProcAddress (я отметил их жёлтыми звездами). Делается это так:



• Встаём на любом свободном месте



• Ctrl+E –> Поле ASCII



• Вписываем необходимые данные



• OK



Теперь нужно все изменённые данные сохранить в исполняемый файл. ПКМ –> Copy to executable –> All modifications –> Copy All –> ПКМ –> Save File. Как только сохранение произведено, копируем нашу собранную DLL в папку Bin, где у нас расположен изменённый файл дельфовой среды и запускаем сам екзешник. Если всё было сделано верно, то мы не получим никаких ексепшенов, а перед нами будет красоваться наша форма, с предложением выбора активных мастеров.



Заключение



Основываясь на данном методе, можно внедрить в среду ещё не одну новую возможность. Главное – фантазия. Я согласен, что пример мягко говоря неудачный, но он довольно хорошо отображает саму суть внедрения кода. Можно было бы обойтись даже без библиотеки, написав ассемблерный код на чистом API. Но это долгое и утомительное занятие. Я буду очень рад, если увижу в комментариях, другие, более полезные применения этой технике. Спасибо за внимание.



Приложение. Исходный код формы.



unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Registry, ComCtrls,IniFiles;
type
TfrmMain = class(TForm)
btnNext: TButton;
lbl3: TLabel;
lbl2: TLabel;
lbl1: TLabel;
lvActive: TListView;
lvInactive: TListView;
procedure btnNextClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure lvInactiveDblClick(Sender: TObject);
procedure lvActiveDblClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{
Метод перечисляет все визарды из реестра и заполняет
lvActive (TListView)
}
procedure GetDelphiWizards;
{
Метод копирует значение (строку) из одного ListView в
другой, в зависимости от переданых аргументов
}
procedure CopyLvStr(Source:TListView; Dest: TListView);
{
Метод сохраняет список неактивных визардов в ini-файл
}
procedure SaveInactiveLst;
{
Метод загружает из ini-файла список неактивных визардов,
заполняет lvInactive (TListView)
}
procedure LoadInnactiveLst;
{
Метод получает полный путь до dll-библиотеки
}
function GetModuleFileNameStr(Instance: THandle): string;
end;

const
ExpertsPath = '\Software\Borland\Delphi\7.0\Experts';
var
frmMain: TfrmMain;

implementation

{$R *.dfm}
{ TfrmMain }

procedure TfrmMain.btnNextClick(Sender: TObject);
begin
Close;
end;

procedure TfrmMain.CopyLvStr(Source, Dest: TListView);
var
Item: TListItem;
begin
Item := Dest.Items.Add;
Item.Caption := Source.Selected.Caption;
Item.SubItems.Add(Source.Selected.SubItems.Strings[0]);
Source.Selected.Delete;
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if lvInactive.Items.Count > 0 then
SaveInactiveLst;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
try
GetDelphiWizards;
LoadInnactiveLst;
except
end;
end;

procedure TfrmMain.GetDelphiWizards;
var
oReg: TRegistry;
vNames: TStringList;
ListItem : TListItem;
i: Integer;
begin
oReg := TRegistry.Create;
oReg.RootKey := HKEY_CURRENT_USER;
oReg.OpenKey(ExpertsPath,FALSE);
vNames := TStringList.Create;
oReg.GetValueNames(vNames);
if vNames.Count > 0 then
begin
for i := 0 to vNames.Count - 1 do
begin
ListItem := lvActive.Items.Add;
ListItem.Caption := vNames.Strings[i];
ListItem.SubItems.Add(oReg.ReadString(vNames.Strings[i]));
end;
end;
vNames.Free;
oReg.Free;
end;

function TfrmMain.GetModuleFileNameStr(Instance: THandle): string;
var
buffer: array [0..MAX_PATH] of Char;
begin
GetModuleFileName( Instance, buffer, MAX_PATH);
Result := buffer;
end;

procedure TfrmMain.LoadInnactiveLst;
var
oIni: TIniFile;
i:integer;
slExperts : TStringList;
Item: TListItem;
begin
slExperts := TStringList.Create;
oIni := TIniFile.Create(ExtractFilePath(GetModuleFileNameStr(Hinstance))+'ide.ini');
oIni.ReadSectionValues('Experts',slExperts);
for i := 0 to slExperts.Count - 1 do
begin
Item := lvInactive.Items.Add;
Item.Caption := slExperts.Names[i];
Item.SubItems.Add(slExperts.Values[slExperts.Names[i]]);

{ удалим ключ из ini-файла }
oIni.DeleteKey('Experts',slExperts.Names[i]);
end;
oIni.Free;
slExperts.Free;
end;

procedure TfrmMain.lvActiveDblClick(Sender: TObject);
var
oReg : TRegistry;
begin
{ удалим значение из реестра }
oReg := TRegistry.Create;
oReg.RootKey := HKEY_CURRENT_USER;
oReg.OpenKey(ExpertsPath,FALSE);
oReg.DeleteValue(lvActive.Selected.Caption);
{ копируем строку из lvActive в lvInactive }
CopyLvStr(lvActive,lvInactive);
end;

procedure TfrmMain.lvInactiveDblClick(Sender: TObject);
var
oReg : TRegistry;
begin
{ добавим значение в реестр (инсталлируем мастер) }
oReg := TRegistry.Create;
oReg.RootKey := HKEY_CURRENT_USER;
oReg.OpenKey(ExpertsPath,FALSE);
oReg.WriteString(lvInactive.Selected.Caption,lvInactive.Selected.SubItems.Strings[0]);
{ копируем строку из lvInactive в lvActive }
CopyLvStr(lvInactive,lvActive);
end;

procedure TfrmMain.SaveInactiveLst;
var
oIni: TIniFile;
i:integer;
begin
oIni := TIniFile.Create(ExtractFilePath(GetModuleFileNameStr(Hinstance))+'ide.ini');
for i := 0 to lvInactive.Items.Count - 1 do
oIni.WriteString('Experts',
lvInactive.Items.Item[i].Caption,
lvInactive.Items.Item[i].SubItems.Strings[0]
);
end;
end.

7 комментариев:

  1. Отличный пример по использованию OllyDbg-a для взлома программ.
    Но ломать свою Delphi - за что же? :D

    ОтветитьУдалить
  2. >> Но ломать свою Delphi - за что же? :D
    Вопрос из вопросов ) Сам думал про это...

    ОтветитьУдалить
  3. =)))))
    Но за статью спасибо.
    Очень приятно читать твой блог о Delphi, содержащий действительно уникальную и интересную информацию.

    ОтветитьУдалить
  4. Алексей, спасибо за хорошие слова. Приятно. А приятнее всего то, что похвалили мой блог именно вы :) У вас тоже интересный блог, я на самом деле давненько вас почитывал, хоть и оставался в тени так сказать (не комментировал). Вот решился на блог по делфи глядя на вас кстати. =))

    ОтветитьУдалить
  5. Я мож чего не уловил... зачем использовать инжект, если есть документированный API экспертов?

    ОтветитьУдалить
  6. Ну тогда, скорее всего и правда, не уловил =) Це ж просто пример, это не эксперт, который можно запихать туда или сюда, это инжект своего кода с саму делфи. То есть какой код, куда и для чего инжектить - нужно придумать ) Это у меня просто пример такой плохенький. Как бы, тут улавливать нужно не в сторону экспертов ;)

    ОтветитьУдалить