Главная  |  Аккуант  |  Форум  |  Ссылки  |  Файлы  |  Статьи
Поиск  
Навигация
 Домой
 Новости
Разделы
Архив новостей
 Сокровищница
e-Books
Компаненты Delphi
Софт
 Статьи
Рейтинг статей
Delphi
В помощь сисадмину
*NIX & *BSD сиcтемы
Познавательное
Архив статей
 ЧаВо(FAQ)
 Форум
Популярные темы
Активные темы
Архив
 Сайты партнеров
 Каталог ссылок
Добавить ссылку
Рейтинг
 Связь с нами

Друзья сайта

Сейчас на сайте
 1: Гости
 0: Пользователи
 312: Пользователи с регистрацией

Вы гость здесь.
+ регистрация

Приглашаю на новый проект

On-line журнал o Linux
Здесь вы найдёте статьи по GNU/Linux Ubuntu/Debian , а так же по другой компьютерной тематике, ответы на вопросы, полезные утилиты. Так же на сайте собраны авторские и другие материалы - статьи, руководства, ФАКи, расказывающие о работе с GNU/Linux Ubuntu/Debian, а так же о ее возможном применение, как настольной или как серверной системы. На сайте, в любое время к вашим услугам форум сможете пообщаться c умными и интересными людьми или задать коллегам в одном из форумов свой вопрос, на который вам непременно ответят. Мы надеемся, что вам здесь понравится и вы будете возвращаться сюда снова и снова.

Ubuntu — это разрабатываемая сообществом, основанная на ядре Linux операционная система, которая идеально подходит для использования на персональных компьютерах, ноутбуках и серверах. Она содержит все необходимые программы, которые вам нужны: программу просмотра Интернет, офисный пакет для работы с текстами, электронными таблицами и презентациями, программы для общения в Интернет и много других. Узнайте больше об Ubuntu >
Debian — это свободная операционная система (ОС) и набор прикладных программ для вашего компьютера. В Debian используется ядро Linux, но большинство утилит ОС разработано в рамках проекта GNU; поэтому полное название проекта — Debian GNU/Linux. Как уже было отмечено выше, Debian GNU/Linux — это не только операционная система. В его состав входит более 18733 пакетов заранее скомпилированного программного обеспечения, которые легко могут быть установлены. О Debian


6.12.06 11:02 | Перехват API функций в Windows NT (часть 2). Методы внедрения кода.
Раздел: Delphi | Автор: ceval | Рейтинг: 7.00 (8) Оценить | Хитов 1589

Предисловие


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


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

Все приемы написания внедряемого кода описанные здесь предназначены для использования в Borland Delphi (6, 7, 2005), но с минимальными изменениями могут быть использованы и в среде Microsoft Visual C++. В среде Borland C++ Builder некоторые приведенные приёмы неосуществимы (в связи с особенностями генерации кода компилятором).




Требования к внедряемому коду:


1) Базонезависимость (адрес загрузки кода в чужой процесс неизвестен заранее).
2) Независимость от RTL (Run Time Library).
3) Использование только библиотек загруженных в АП (адресное пространство) целевого процесса.
4) Наличие в внедряемом коде всех необходимых для него данных.


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

Адреса функций из ntdll.dll и kernel32.dll можно передавать в внедряемый код из исходного процесса, т.к. эти библиотеки всегда загружаются по одинаковым адресам во всех процессах. Другие системные библиотеки (user32.dll, shell32.dll) тоже часто загружаются по одинаковым адресам, но так происходит только в том случае, если исходный и целевой процессы импортируют эти библиотеки статически, в случае же динамического импорта в следствии коллизии модулей они могут быть загружены по разным адресам. Все остальные библиотеки обычно загружаются по разным адресам в разных процессах даже при использовании статического импорта. При написании внедряемого кода следует учесть, что единственная DLL которая обязательно должна присутствовать в адресном пространстве любого процесса - это ntdll.dll, эта DLL загружается даже при отсутствии импорта в исполнимом файле, и представляет собой слой Native API, переходники к функциям ядра Windows.
Компилятор Delphi обычно генерирует в процедурах короткие переходы (JMP SHORT), но при большом размере процедуры могут появиться переходы по абсолютным адресам, поэтому внедряемый код желательно писать используя короткие процедуры, используя как можно меньше условий и циклов.
В случае возникновения проблем с внедряемым кодом, необходимо воспользоваться отладчиком и убедиться в том, что сгенерированный код удовлетворяет вышеприведенным условиям.


Если писать внедряемый код на delphi, то в нем нельзя использовать объекты, строки типа string, а также любые другие типы данных опирающиеся на функциональность Delphi Run Time Library. Вместо string следует использовать PChar и динамически, с помощью VirtualAlloc выделять память под строку, либо хранить строку в стеке объявляя её в локальных переменных как array of Char.




Использование API из внедряемого кода:


Если внедряемый код использует функции экспортируемые библиотеками отличными от ntdll.dll, то необходимо убедиться в наличии этих библиотек в АП целевого процесса, и при необходимости загрузить их.
Для загрузки библиотеки можно использовать функцию LdrLoadDll из ntdll.dll.


Код:
function LdrLoadDll(szcwPath: PWideChar;
pdwLdrErr: dword;
pUniModuleName: PUnicodeString;
pResultInstance: PDWORD): NTSTATUS;
stdcall; external 'ntdll.dll';

Нас интересует параметр pUniModuleName представляющий из себя указатель на строку типа UnicodeString в которой передается имя загружаемой DLL. По указателю pResultInstance будет сохранен адрес MZ заголовка загруженной DLL (параметр hInstance).

Следующий код загружает DLL аналогично функции kernel32 LoadLibraryW:



Код:

Function MyLoadLibrary(lpLibFileName: PWideChar): HMODULE;
var
uName: TUnicodeString;
begin
RtlInitUnicodeString(@uName, lpLibFileName);
if (LdrLoadDll(nil, 0, @uName, @Result) > 0) then Result := 0;
RtlFreeUnicodeString(@uName);
end;

Для получения адреса функции чледует использовать LdrGetProcedureAddress.


Код:
function LdrGetProcedureAddress(hModule: dword;
dOrdinal: DWORD;
psName: PAnsiString;
ppProcedure: ppointer): NTStatus;
stdcall; external 'ntdll.dll';


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





Внедрение процедуры в процесс:


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

function InjectMemory(Process: dword; Memory: pointer; Size: dword): pointer;
var
BytesWritten: dword;
begin
Result := VirtualAllocEx(Process, nil, Size, MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Process, Result, Memory, Size, BytesWritten);
end;


Эта процедура предельно проста, она принимает хэндл открытого процесса, указатель на данные в текущем процессе и размер данных, а возвращает указатель на данные в целевом процессе.


Внедрение процедуры в целевой процесс:
Код:

program InjectCode;

uses
Windows,
advApiHook;

type
TRemoteInfo = record
LoadLibrary: function(lpLibFileName: PChar): HMODULE; stdcall;
GetProcAddress: function(hModule: HMODULE;
lpProcName: LPCSTR): FARPROC; stdcall;
Kernel32 : array[0..16] of Char;
User32 : array[0..16] of Char;
MessageBoxA : array[0..16] of Char;
nExitThread : array[0..16] of Char;
Text : array[0..16] of Char;
Title : array[0..16] of Char;
end;

{ Процедура внедряемая в процесс }
procedure RemoteThread(RemoteInfo: pointer); stdcall;
var
MessageBox: function(hWnd: HWND; lpText,
lpCaption: PChar; uType: UINT): Integer; stdcall;
ExitThread: procedure(uExitCode: UINT); stdcall;
begin
with TRemoteInfo(RemoteInfo^) do
begin
@MessageBox := GetProcAddress(LoadLibrary(User32), MessageBoxA);
@ExitThread := GetProcAddress(LoadLibrary(Kernel32), nExitThread);
MessageBox(0, Text, Title, 0);
ExitThread(0);
end;
end;
procedure RemoteThreadEnd; begin end; //метка конца кода

var
RemoteInfo: TRemoteInfo;
pInfo, CodeAdr: pointer;
TID: dword;
Process: dword;
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;

begin
//Запускаем процесс
ZeroMemory(@StartInfo, SizeOf(TStartupInfo));
StartInfo.cb := SizeOf(TStartupInfo);
CreateProcess(nil, 'notepad.exe', nil, nil, False, 0,
nil, nil, StartInfo, ProcInfo);
Process := ProcInfo.hProcess;
//Заполняем структуру передаваемую внедряемому коду
lstrcpy(RemoteInfo.User32, 'user32.dll');
lstrcpy(RemoteInfo.Kernel32, 'kernel32.dll');
lstrcpy(RemoteInfo.MessageBoxA, 'MessageBoxA');
lstrcpy(RemoteInfo.nExitThread, 'ExitThread');
lstrcpy(RemoteInfo.Text, 'Hello World!');
lstrcpy(RemoteInfo.Title, 'Injected MessageBox');
//получаем адреса используемых API
@RemoteInfo.LoadLibrary := GetProcAddress(GetModuleHandle('kernel32.dll'),
'LoadLibraryA');
@RemoteInfo.GetProcAddress := GetProcAddress(GetModuleHandle('kernel32.dll'),
'GetProcAddress');
//копируем в процесс структуру с данными
pInfo := InjectMemory(Process, @RemoteInfo, SizeOf(TRemoteInfo));
//копируем в процесс внедряемый код
CodeAdr := InjectMemory(Process, @RemoteThread,
dword(@RemoteThreadEnd) - dword(@RemoteThread));
//запускаем внедренный код
CreateRemoteThread(Process, nil, 0, CodeAdr, pInfo, 0, TID);
end.


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


Для облегчения программирования подобных вещей введем еще одну процедуру:
Код:

function InjectThread(Process: dword; Thread: pointer; Info: pointer;
InfoLen: dword; Results: boolean): THandle;
var
pThread, pInfo: pointer;
BytesRead, TID: dword;
begin
pInfo := InjectMemory(Process, Info, InfoLen);
pThread := InjectMemory(Process, Thread, SizeOfProc(Thread));
Result := CreateRemoteThread(Process, nil, 0, pThread, pInfo, 0, TID);
if Results then
begin
WaitForSingleObject(Result, INFINITE);
ReadProcessMemory(Process, pInfo, Info, InfoLen, BytesRead);
end;
end;


Эта процедура копирует в целевой процесс внедряемый код и структуру с данными для него, после чего запускает внедренный код.

Принимаемые параметры:

Process  - хэндл открытого процесса.

Thread   - указатель на внедряемый код в текущем процессе.
Info        - указатель на структуру с данными.
InfoLen - размер структуры с данными.

Results - необходимость возврата результата. (если true, то функция ожидает завершения удаленного потока и копирует обратно структуру с данными) .
Для определения размера внедряемого кода используется функция SizeOfProc, которая приведена далее.




Совершенствуем метод перехвата:


В предыдущей статье был описан метод перехвата путем замены первых 5 байт перехватываемой функции своими, для вызова реальной (true) функции нужно было установить обратно замененные байты, а после вызова - снова установить перехват.

Недостаток этого метода в том, что в многопоточных приложениях в моменты снятия - установки перехвата возможен вызов функции другим потоком напрямую, или возникновение критической ошибки в случае прерывания потока в момент записи участка кода. Эти недостатки можно устранить останавливая и запуская побочные потоки приложения каждый раз при вызове true функции, но это приведет к значительному снижению производительности, либо даже к взаимным блокировкам потоков (например при перехвате WaitForSingleObject).
Для устранения недостатков этого метода, следует его немного модифицировать. Необходимо выделить в памяти буфер, и скопировать туда участок начала перехватываемой функции содержащий целое число команд и имеющий длину >= 5 байт, после чего в его конце поставить jmp на следующую команду перехватываемой функции после сохраненного участка. После чего сформированный таким образом код можно будет вызывать в качестве true функции.
Этот метод имеет все достоинства и лишен недостатков предыдущего. Он обеспечивает корректную работу в многопоточном приложении и быстрый вызов true функций. Написание кода перехватчиков в этом случае также значительно упрощается.


Все эти действия производит следующая функция:
Код:

{<i>
Установка перехвата функции.
TargetProc - адрес перехватываемой функции,
NewProc - адрес функции замены,
OldProc - здесь будет сохранен адрес моста к старой функции.
</i>}
function HookCode(TargetProc, NewProc: pointer; var OldProc: pointer): boolean;
var
Address: dword;
OldProtect: dword;
OldFunction: pointer;
Proc: pointer;
begin
Result := False;
try
Proc := TargetProc;
//вычисляем адрес относительного (jmp near) перехода на новую функцию
Address := dword(NewProc) - dword(Proc) - 5;
VirtualProtect(Proc, 5, PAGE_EXECUTE_READWRITE, OldProtect);
//создаем буффер для true функции
GetMem(OldFunction, 255);
//копируем первые 4 байта функции
dword(OldFunction^) := dword(Proc);
byte(pointer(dword(OldFunction) + 4)^) := SaveOldFunction(Proc, pointer(dword(OldFunction) + 5));
//byte(pointer(dword(OldFunction) + 4)^) - длина сохраненного участка
byte(Proc^) := $e9; //устанавливаем переход
dword(pointer(dword(Proc) + 1)^) := Address;
VirtualProtect(Proc, 5, OldProtect, OldProtect);
OldProc := pointer(dword(OldFunction) + 5);
except
Exit;
end;
Result := True;
end;


Для сохранения участка содержащего целое число команд используется функция SaveOldFunction:


Код:
function SaveOldFunction(Proc: pointer; Old: pointer): dword;
var
SaveSize, Size: dword;
Next: pointer;
begin
SaveSize := 0;
Next := Proc;
//сохраняем следующие несколько коротких, либо одну длинную инструкцию
while SaveSize < 5 do
begin
Size := SizeOfCode(Next);
Next := pointer(dword(Next) + Size);
Inc(SaveSize, Size);
end;
CopyMemory(Old, Proc, SaveSize);
//генерируем переход на следующую инструкцию после сохраненного участка
byte(pointer(dword(Old) + SaveSize)^) := $e9;
dword(pointer(dword(Old) + SaveSize + 1)^) := dword(Next) - dword(Old) - SaveSize - 5;
Result := SaveSize;
end;

Для снятия перехвата достаточно прочитать количество сохраненных байт и записать их обратно в начало перехватываемой функции.

Это делает следующий код:


Код:
{<i>
Снятие перехвата установленного по HookCode,
OldProc - адрес моста возвращенный функцией HookCode.
</i>}
function UnhookCode(OldProc: pointer): boolean;
var
OldProtect: dword;
Proc: pointer;
SaveSize: dword;
begin
Result := True;
try
Proc := pointer(dword(pointer(dword(OldProc) - 5)^));
SaveSize := byte(pointer(dword(OldProc) - 1)^);
VirtualProtect(Proc, 5, PAGE_EXECUTE_READWRITE, OldProtect);
CopyMemory(Proc, OldProc, SaveSize);
VirtualProtect(Proc, 5, OldProtect, OldProtect);
FreeMem(pointer(dword(OldProc) - 5));
except
Result := False;
end;
end;


Так как чаще всего приходиться перехватывать функции экспортируемые DLL, то для упрощения задачи введем еще одну функцию:

Код:

{<i>
Установка перехвата функции из Dll в текущем процессе.
lpModuleName - имя модуля,
lpProcName - имя функции,
NewProc - адрес функции замены,
OldProc - здесь будет сохранен адрес моста к старой функции.
В случае отсутствия модуля в текущем АП, будет сделана попытка его загрузить.</i>
}
function HookProc(lpModuleName, lpProcName: PChar;
NewProc: pointer; var OldProc: pointer): boolean;
var
hModule: dword;
fnAdr: pointer;
begin
Result := false;
hModule := GetModuleHandle(lpModuleName);
if hModule = 0 then hModule := LoadLibrary(lpModuleName);
if hModule = 0 then Exit;
fnAdr := GetProcAddress(hModule, lpProcName);
if fnAdr = nil then Exit;
Result := HookCode(fnAdr, NewProc, OldProc);
end;




Внедрение процесса целиком:


Рассмотренный здесь метод внедрения отдельных процедур кода непрост по причине необходимости четко разграничить код и данные и формировать их отдельно. Но можно сделать куда проще - внедрить в адресное пространство целевого процесса весь образ текущего процесса целиком (код данные ресурсы и.т.д.) после чего запустить на выполнение и работать также, как и в своем процессе. Этот метод позволяет работать во внедряемом кода с RTL и применять обьектно ориентированное программирование, к тому же сам метод чрезвычайно прост для применения.
Для внедрения образа текущего процесса введем следующую функцию:
Код:

{<i>
Внедрение образа текущего процесса в чужое адресное пространство.
EntryPoint - адрес точки входа внедренного кода.</i>
}
function InjectThisExe(Process: dword; EntryPoint: pointer): boolean;
var
Module, NewModule: pointer;
Size, TID: dword;
hThread : dword;
BytesWritten: dword;
begin
Result := False;
Module := pointer(GetModuleHandle(nil));
Size := PImageOptionalHeader(pointer(integer(Module) +
PImageDosHeader(Module)._lfanew + SizeOf(dword) +
SizeOf(TImageFileHeader))).SizeOfImage;
NewModule := VirtualAllocEx(Process, Module, Size, MEM_COMMIT or
MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if NewModule = nil then exit;
WriteProcessMemory(Process, NewModule, Module, Size, BytesWritten);
hThread := CreateRemoteThread(Process, nil, 0, EntryPoint, NewModule, 0, TID);
if hThread <> 0 then Result := True;
end;

Она копирует полный образ исполнимого файла на диске в АП целевого процесса и запускает его выполнение в отдельном потоке с точки EntryPoint.


То же самое можно произвести и с любым другим образом исполнимого файла:


Код:
{<i>
Внедрение образа Exe файла в чужое адресное пространство и запуск его точки входа.
Data - адрес образа файла в текущем процессе.</i>
}
function InjectExe(Process: dword; Data: pointer): boolean;
var
Module, NewModule: pointer;
EntryPoint: pointer;
Size, TID: dword;
hThread : dword;
BytesWritten: dword;
Header: PImageOptionalHeader;
begin
Result := False;
Header := PImageOptionalHeader(pointer(integer(Data) +
PImageDosHeader(Data)._lfanew + SizeOf(dword) +
SizeOf(TImageFileHeader)));
Size := Header^.SizeOfImage;
Module := pointer(Header^.ImageBase);
EntryPoint := pointer(Header^.ImageBase + Header^.AddressOfEntryPoint);

NewModule := VirtualAllocEx(Process, Module, Size, MEM_COMMIT or
MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if NewModule = nil then exit;
WriteProcessMemory(Process, NewModule, Module, Size, BytesWritten);
hThread := CreateRemoteThread(Process, nil, 0, EntryPoint, NewModule, 0, TID);
if hThread <> 0 then Result := True;
end;




Приведем пример использования этого метода:



Код:
program InjectProcess;

{$IMAGEBASE $13140000}

uses
Windows,
advApiHook;

var
StartInf: TStartupInfo;
ProcInf: TProcessInformation;

function Main(dwEntryPoint: Pointer): dword; stdcall;
begin
LoadLibrary('kernel32.dll');
LoadLibrary('user32.dll');
MessageBox(0, 'Hello World', 'Process Injection', 0);
ExitThread(0);
end;

begin
//запускаем блокнот
ZeroMemory(@StartInf, SizeOf(TStartupInfo));
CreateProcess(nil, 'notepad.exe', nil, nil, false,
0, nil, nil, StartInf, ProcInf);
//внедряем в него образ текущего процесса
InjectThisExe(ProcInf.hProcess, @Main);
end.



В этом коде следует обратить внимание на директиву {$IMAGEBASE $13140000}, она заставляем компилятор генерировать исполняемый образ с базой загрузки 13140000h. Адрес ImageBase следует выбирать таким, чтобы память в целевом процессе по этому адресу не была занята. Также перед тем, как использовать какие либо API вызовы из внедряемого кода, следует загрузить используемые DLL библиотеки с помощью LoadLibrary, так как в целевом процессе их может не оказаться.

Я думаю, вы уже заметили, что этот метод имеет одно единственное ограничение - это невозможность загрузки по адресу отличному от ImageBase. Если по этому адресу память в целевом процессе будет занята, то можно её освободить с помощью VirtualFree, но ни к чему хорошему это не приведет, так поступать можно только тогда, когда нужно полностью заменить целевой процесс, и перед этим необходимо остановить все его потоки. Тогда после загрузки исполняемого образа можно будет освободить всю связанную с заменяемым процессом память и уничтожить все его потоки.
Также к недостаткам этого метода можно отнести необходимость загрузки библиотек используемых внедряемым процессом, а это значительно снижает скрытность перехвата.

Существует способ преодолеть привязку внедряемого процесса к ImageBase, для этого нужно заставить компилятор генерировать в исполнимом файле reloc секцию, а после загрузки образа по произвольному адресу настраивать релоки (процедура настройки релоков приведена далее).





Усовершенствованный метод DLL Injection:



Для перехвата API в другом процессе весьма удобен и эффективен метод внедрения в него своей DLL, но этот метод имеет некоторые недостатки, так как необходимо хранить DLL на диске, и загрузку лишней DLL легко обнаружить программами типа PE-Tools. Также на лишнюю DLL могут заругаться антивирусы и фаерволлы (например Outpost Fierwall) что тоже нежелательно.
Но существует метод позволяющий загрузить DLL в другой процесс более незаметным способом. Для этого нужно внедрить в процесс образ этой DLL, затем настроить у нее таблицу импорта и релоки, после чего выполнить ее точку входа. Этот метод позволяет не хранить DLL на диске, а проводить действия с ней исключительно в памяти, также эта DLL не будет видна в списке загруженных процессом модулей, и на нее не заругается фаерволл.

Следующий код копирует и настраивает DLL в выделенный участок чужого адресного пространства:
Код:

{<i>
Отображение Dll на чужое адресное пространство, настройка импорта и релоков.
Process - хэндл процесса для отображения,
Dest - адрес отображения в процессе Process,
Src - адрес образа Dll в текущем процессе.</i>
}
function MapLibrary(Process: dword; Dest, Src: pointer): TLibInfo;
var
ImageBase: pointer;
ImageBaseDelta: integer;
ImageNtHeaders: PImageNtHeaders;
PSections: ^TSections;
SectionLoop: integer;
SectionBase: pointer;
VirtualSectionSize, RawSectionSize: dword;
OldProtect: dword;
NewLibInfo: TLibInfo;

{<i> Настройка релоков </i>}
procedure ProcessRelocs(PRelocs:PImageBaseRelocation);
var
PReloc: PImageBaseRelocation;
RelocsSize: dword;
Reloc: PWord;
ModCount: dword;
RelocLoop: dword;
begin
PReloc := PRelocs;
RelocsSize := ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
while dword(PReloc) - dword(PRelocs) < RelocsSize do
begin
ModCount := (PReloc.SizeOfBlock - Sizeof(PReloc^)) div 2;
Reloc := pointer(dword(PReloc) + sizeof(PReloc^));
for RelocLoop := 0 to ModCount - 1 do
begin
if Reloc^ and $f000 <> 0 then Inc(pdword(dword(ImageBase) +
PReloc.VirtualAddress +
(Reloc^ and $0fff))^, ImageBaseDelta);
Inc(Reloc);
end;
PReloc := pointer(Reloc);
end;
end;

{<i> Настройка импорта Dll в чужом процессе</i>}
procedure ProcessImports(PImports: PImageImportDescriptor);
var
PImport: PImageImportDescriptor;
Import: pdword;
PImportedName: pchar;
ProcAddress: pointer;
PLibName: pchar;
ImportLoop: integer;

function IsImportByOrdinal(ImportDescriptor: dword): boolean;
begin
Result := (ImportDescriptor and IMAGE_ORDINAL_FLAG32) <> 0;
end;

begin
PImport := PImports;
while PImport.Name <> 0 do
begin
PLibName := pchar(dword(PImport.Name) + dword(ImageBase));
if not Find(NewLibInfo.LibsUsed, PLibName, ImportLoop) then
begin
InjectDll(Process, PLibName);
Add(NewLibInfo.LibsUsed, PLibName);
end;
if PImport.TimeDateStamp = 0 then
Import := pdword(pImport.FirstThunk + dword(ImageBase))
else
Import := pdword(pImport.OriginalFirstThunk + dword(ImageBase));

while Import^ <> 0 do
begin
if IsImportByOrdinal(Import^) then
ProcAddress := GetProcAddressEx(Process, PLibName, PChar(Import^ and $ffff), 4)
else
begin
PImportedName := pchar(Import^ + dword(ImageBase) + IMPORTED_NAME_OFFSET);
ProcAddress := GetProcAddressEx(Process, PLibName, PImportedName, Length(PImportedName));
end;
Ppointer(Import)^ := ProcAddress;
Inc(Import);
end;
Inc(PImport);
end;
end;

begin
ImageNtHeaders := pointer(dword(Src) + dword(PImageDosHeader(Src)._lfanew));
ImageBase := VirtualAlloc(Dest, ImageNtHeaders.OptionalHeader.SizeOfImage,
MEM_RESERVE, PAGE_NOACCESS);

ImageBaseDelta := dword(ImageBase) - ImageNtHeaders.OptionalHeader.ImageBase;
SectionBase := VirtualAlloc(ImageBase, ImageNtHeaders.OptionalHeader.SizeOfHeaders,
MEM_COMMIT, PAGE_READWRITE);
Move(Src^, SectionBase^, ImageNtHeaders.OptionalHeader.SizeOfHeaders);
VirtualProtect(SectionBase, ImageNtHeaders.OptionalHeader.SizeOfHeaders,
PAGE_READONLY, OldProtect);
PSections := pointer(pchar(@(ImageNtHeaders.OptionalHeader)) +
ImageNtHeaders.FileHeader.SizeOfOptionalHeader);

for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
begin
VirtualSectionSize := PSections[SectionLoop].Misc.VirtualSize;
RawSectionSize := PSections[SectionLoop].SizeOfRawData;
if VirtualSectionSize < RawSectionSize then
begin
VirtualSectionSize := VirtualSectionSize xor RawSectionSize;
RawSectionSize := VirtualSectionSize xor RawSectionSize;
VirtualSectionSize := VirtualSectionSize xor RawSectionSize;
end;
SectionBase := VirtualAlloc(PSections[SectionLoop].VirtualAddress +
pchar(ImageBase), VirtualSectionSize,
MEM_COMMIT, PAGE_READWRITE);
FillChar(SectionBase^, VirtualSectionSize, 0);
Move((pchar(src) + PSections[SectionLoop].pointerToRawData)^,
SectionBase^, RawSectionSize);
end;
NewLibInfo.DllProcAddress := pointer(ImageNtHeaders.OptionalHeader.AddressOfEntryPoint +
dword(ImageBase));
NewLibInfo.DllProc := TDllEntryProc(NewLibInfo.DllProcAddress);

NewLibInfo.ImageBase := ImageBase;
NewLibInfo.ImageSize := ImageNtHeaders.OptionalHeader.SizeOfImage;
SetLength(NewLibInfo.LibsUsed, 0);
if ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0
then ProcessRelocs(pointer(ImageNtHeaders.OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].
VirtualAddress + dword(ImageBase)));

if ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress <> 0
then ProcessImports(pointer(ImageNtHeaders.OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
VirtualAddress + dword(ImageBase)));

for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
VirtualProtect(PSections[SectionLoop].VirtualAddress + pchar(ImageBase),
PSections[SectionLoop].Misc.VirtualSize,
GetSectionProtection(PSections[SectionLoop].Characteristics),
OldProtect);
Result := NewLibInfo;
end;



Теперь для внедрения DLL в целевой процесс этим способом можно использовать следующую функцию:
Код:

{<i>
Внедрение Dll в процесс методом инжекции кода и настройки образа Dll в памяти.
Данный метод внедрения более скрытен, и не обнаруживается фаерволлами.</i>
}
function InjectDllEx(Process: dword; Src: pointer): boolean;
type
TDllLoadInfo = packed record
Module: pointer;
EntryPoint: pointer;
end;
var
Lib: TLibInfo;
BytesWritten: dword;
ImageNtHeaders: PImageNtHeaders;
pModule: pointer;
Offset: dword;
DllLoadInfo: TDllLoadInfo;
hThread: dword;

{<i> процедура передачи управления на точку входа dll</i> }
procedure DllEntryPoint(lpParameter: pointer); stdcall;
var
LoadInfo: TDllLoadInfo;
begin
LoadInfo := TDllLoadInfo(lpParameter^);
asm
xor eax, eax
push eax
push DLL_PROCESS_ATTACH
push LoadInfo.Module
call LoadInfo.EntryPoint
end;
end;

begin
Result := False;
ImageNtHeaders := pointer(dword(Src) + dword(PImageDosHeader(Src)._lfanew));
Offset := $10000000;
repeat
Inc(Offset, $10000);
pModule := VirtualAlloc(pointer(ImageNtHeaders.OptionalHeader.ImageBase + Offset),
ImageNtHeaders.OptionalHeader.SizeOfImage,
MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if pModule <> nil then
begin
VirtualFree(pModule, 0, MEM_RELEASE);
pModule := VirtualAllocEx(Process, pointer(ImageNtHeaders.OptionalHeader.
ImageBase + Offset),
ImageNtHeaders.OptionalHeader.
SizeOfImage,
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
end;
until ((pModule <> nil) or (Offset > $30000000));
Lib := MapLibrary(Process, pModule, Src);
if Lib.ImageBase = nil then Exit;
DllLoadInfo.Module := Lib.ImageBase;
DllLoadInfo.EntryPoint := Lib.DllProcAddress;
WriteProcessMemory(Process, pModule, Lib.ImageBase, Lib.ImageSize, BytesWritten);
hThread := InjectThread(Process, @DllEntryPoint, @DllLoadInfo,
SizeOf(TDllLoadInfo), False);
if hThread <> 0 then Result := True
end;


В приведенном коде, как вы наверное заметили используется функция GetProcAddressEx для получения адреса API в целевом процессе.

Эта задача также выполняется с помощью внедрения кода и создания удаленных потоков.

Вот полный код этой функции:

Код:

{<i> Получение адреса API в чужом адресном пространстве </i>}
function GetProcAddressEx(Process: dword; lpModuleName,
lpProcName: pchar; dwProcLen: dword): pointer;
type
TGetProcAddrExInfo = record
pExitThread: pointer;
pGetProcAddress: pointer;
pGetModuleHandle: pointer;
lpModuleName: pointer;
lpProcName: pointer;
end;
var
GetProcAddrExInfo: TGetProcAddrExInfo;
ExitCode: dword;
hThread: dword;

procedure GetProcAddrExThread(lpParameter: pointer); stdcall;
var
GetProcAddrExInfo: TGetProcAddrExInfo;
begin
GetProcAddrExInfo := TGetProcAddrExInfo(lpParameter^);
asm
push GetProcAddrExInfo.lpModuleName
call GetProcAddrExInfo.pGetModuleHandle
push GetProcAddrExInfo.lpProcName
push eax
call GetProcAddrExInfo.pGetProcAddress
push eax
call GetProcAddrExInfo.pExitThread
end;
end;

begin
Result := nil;
GetProcAddrExInfo.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32.dll'),
'GetModuleHandleA');
GetProcAddrExInfo.pGetProcAddress := GetProcAddress(GetModuleHandle('kernel32.dll'),
'GetProcAddress');
GetProcAddrExInfo.pExitThread := GetProcAddress(GetModuleHandle('kernel32.dll'),
'ExitThread');
if dwProcLen = 4 then GetProcAddrExInfo.lpProcName := lpProcName else
GetProcAddrExInfo.lpProcName := InjectMemory(Process, lpProcName, dwProcLen);

GetProcAddrExInfo.lpModuleName := InjectString(Process, lpModuleName);
hThread := InjectThread(Process, @GetProcAddrExThread, @GetProcAddrExInfo,
SizeOf(GetProcAddrExInfo), False);

if hThread <> 0 then
begin
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, ExitCode);
Result := pointer(ExitCode);
end;
end;



Эта функция используется при заполнении таблицы импорта внедряемой библиотеки адресами API.



  [1] 2 »

  1 2 3 4 5 6 7 8 9 10  

Родственные ссылки
» Другие статьи раздела Delphi
» Эта статья от пользователя ceval

5 cамых читаемых статей из раздела Delphi:
» Прозрачность в Delphi 6
» Delphi и Bluetooth часть1
» Запись сообщений в журнал событий Windows на Delphi
» Delphi и Bluetooth часть2
» Многоязычный интерфейс приложений в Delphi

5 последних статей раздела Delphi:
» Перехват API функций в Windows NT (часть 2). Методы внедрения кода.
» Перехват API функций в Windows NT (часть 1). Основы перехвата
» Функции Win API для работы с окнами
» Прогулка по окнам Windows.
» Создание CHM-файлов с помощью Delphi

¤ Перевести статью в страницу для печати
¤ Послать эту cтатью другу