Simple Dll Injection, detailed explanation
Простой метод внедрения DLL, детальное описание
Language: Delphi / Pascal
Level: Beginner
Note: Статья для новичков с детальным разъяснением процесса внедрения динамических биб-лиотек.
Обычно для внедрения динамических библиотек в адресное пространство процесса применяют три способа:
- Внедрение в третьем кольце
Кольца защиты — архитектура информационной безопасности и функциональной отказо-устойчивости, реализующая аппаратное разделение системного и пользовательского уровней привилегий. Структуру привилегий можно изобразить в виде нескольких концентрических кру-гов. В этом случае системный режим (режим супервизора или нулевое кольцо, т.н. "кольцо 0"), обеспечивающий максимальный доступ к ресурсам, является внутренним кругом, тогда как ре-жим пользователя с ограниченным доступом — внешним.
- Внедрение с помощью реестра
- С помощью удалённых потоков и глобальных ловушек (hooks).
Здесь мы не будем останавливаться на всех методах, а рассмотрим внедрение DLL с помощью удаленных потоков, являющимся гибким и сравнительно простым способом.
Суть данного способа заключается в создании потока загружающего необходимую библиотеку в адресное пространство выбранного процесса. При этом на момент внедрения DLL, целевой процесс должен функционировать в системе и должен быть открыт с правами на создание уда-ленных потоков и записи в собственное адресное пространство.
Сразу приступим к разбору кода.
Функция осуществляющая внедрение библиотеки.
procedure InjectDll(TargetId: Cardinal; DllName: PAnsichar);
var
BytesWrite :cardinal;
ParamAddr : pointer;
pThreadStart : pointer;
Hdl : cardinal;
hThread : cardinal;
hRemoteThread : Cardinal;
begin
// Устанавливаем отладочные привилегии для выбранного процесса, т.к. без данных
// привилегий код внедрения работать не будет
ChangePrivilege('SeDebugPrivilege', True);
// Открываем существующий объект процесса
Hdl := OpenProcess(PROCESS_ALL_ACCESS, false, TargetId);
// Выделяем память под структуру, которая передается нашей функции, под параметры, которые передаются функции
ParamAddr := VirtualAllocEx(Hdl, nil, Length(DllName), MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE);
// Пишем саму структуру
WriteProcessMemory(Hdl, ParamAddr, PAnsichar(DllName), Length(DllName), BytesWrite);
pThreadStart := GetProcAddress(GetModuleHandle('KERNEL32.DLL'), PAnsiChar('LoadLibraryA'));
// Запускаем удаленный поток
hThread := CreateRemoteThread(Hdl, nil, 0, pThreadStart, ParamAddr, 0,hRemoteThread);
// Ждем пока удаленный поток отработает...
WaitForSingleObject(hThread, INFINITE);
Closehandle(hThread);
end;
Функция получения идентификатора процесса по указанному имени исполняемого файла
function GetProcessID(ProcessName : string ) : DWORD ;
var
Handle :tHandle;
Process :tProcessEntry32;
GotProcess :Boolean;
begin
Handle:=CreateToolHelp32SnapShot(TH32CS_SNAPALL,0) ;
Process.dwSize:=SizeOf(Process);
GotProcess := Process32First(Handle,Process);
if GotProcess and (Process.szExeFile<>ProcessName) then
repeat
GotProcess := Process32Next(Handle,Process);
until (not GotProcess) or (Process.szExeFile=ProcessName);
if GotProcess then Result := Process.th32ProcessID
else Result := 0;
CloseHandle(Handle);
end;
Функция установки отладочных привилегий.
procedure ChangePrivilege(szPrivilege: PChar; fEnable: Boolean);
var
NewState: TTokenPrivileges;
luid: TLargeInteger;
hToken: THandle;
ReturnLength: DWord;
begin
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken);
LookupPrivilegeValue(nil, szPrivilege, luid);
NewState.PrivilegeCount := 1;
NewState.Privileges[0].Luid := luid;
if (fEnable) then
NewState.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
else
NewState.Privileges[0].Attributes := 0;
AdjustTokenPrivileges(hToken, False, NewState, SizeOf(NewState), nil, Re-turnLength);
CloseHandle(hToken);
end;
Процедура непосредственного внедрения библиотеки
procedure Inject;
var
pID: cardinal;
Begin
// определяем ProcessId по указанному имени исполняемого файла
pID := GetProcessID('Game.exe');
// вызываем функцию внедрения библиотеки dllToInject.dll в процесс с pID
InjectDll(pID,'dllToInject.dll');
end;
Note: Приведенные функции имеют общий характер, в них отсутствуют элементарные провер-ки на существование файлов и т.д.
Детальное описание используемых функций
1. OpenProcess(PROCESS_ALL_ACCESS, false, TargetId);
Code:
HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
Функция OpenProcess открывает существующий объект процесса.
Аргументы
- dwDesiredAccess - Устанавливает уровень доступа к объекту процесса.
- bInheritHandle - Если этот параметр является ИСТИНА (TRUE), дескриптор наследуем. Если этот параметр является ЛОЖЬ (FALSE), дескриптор не может наследоваться.
- dwProcessId - Идентификатор процесса, который открыт.
Возвращаемые значения
- Если функция завершается успешно, величина возвращаемого значения - открытый де-скриптор заданного процесса.
- Если функция завершается с ошибкой, величина возвращаемого значения ПУСТО (NULL). Чтобы получить дополнительные данные об ошибке, вызовите GetLastError.
2. VirtualAllocEx()
Code:
function VirtualAllocEx(
hProcess: THandle; // процесс в котором выделяется память
lpAddress: Pointer; // начальный адрес региона для резервирования памяти
dwSize: DWORD; // размер региона
flAllocationType: DWORD; // способ выделения
flProtect: DWORD // атрибуты доступа
): Pointer; stdcall;
Функция VirtualAllocEx резервирует или предоставляет физическую память под указанными страницами в виртуальном адресном пространстве указанного процесса. Память, выделяемая этой функцией, автоматически инициализируется нулями, кроме случая, когда указан флаг MEM_RESET.
Аргументы
- hProcess - дескриптор процесса. Функция выделяет память в адресном пространстве именно этого процесса.
- lpAddress - указывает желаемый базовый адрес региона памяти, для которого будет произведено выделение памяти. Если указанная память резервируется, то этот адрес будет округлен вниз до границы 64 Кб. Если память уже зарезервирована и происходит передача памяти, указанный адрес округляется вниз до ближайшей границы страницы. Для того, чтобы определить размер страницы на компьютере, на котором выполняется приложение, необходимо использовать функцию GetSystemInfo. Если желаемый адрес равен nil, то система сама определит место, где будет выделен регион памяти.
- dwSize - указывает размер в байтах региона памяти. Если указанный желаемый адрес lpAddress равен nil, то указанное значение округлится вверх таким образом, чтобы включать целое число страниц. В противном случае выделяемые страницы охватят весь диапазон начинающийся по адресу lpAddress и заканчивающийся по адресу lpAddress + dsSize. В этом случае возможно выделение двух страниц, память на которых будет ис-пользована не полностью (в начале и в конце).
- flAllocationType - указывает способ выделения памяти. Указанный параметр может быть комбинацией следующих значений: MEM_COMMIT, MEM_RESERVE, MEM_RESET, MEM_TOP_DOWN.
- flProtect - указывает тип доступа к памяти. Если указанный диапазон страниц имеет под собой физическую память, вы можете указать одно из следующих значений, по необхо-димости скомбинированных с флагами PAGE_GUARD и PAGE_NOCACHE: PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_GUARD, PAGE_NOACCESS, PAGE_NOCACHE
Возвращаемое значение
- В случае удачного завершения возвращаемым значением является базовый адрес выде-ленных страниц памяти.
- В случае ошибки функция возвращает nil. Для получения расширенной информации об ошибке следует сделать вызов GetLastError.
3. WriteProcessMemory ();
Code:
BOOL WriteProcessMemory(
HANDLE hProcess, // хендл процесса
LPVOID lpBaseAddress, // адрес по которому надо писать
LPVOID lpBuffer, // указатель на буфер
DWORD nSize, // количество байт для записи
LPDWORD lpNumberOfBytesWritten //количество реально записанных байт
);
Функция WriteProcessMemory осуществляет запись данных в память выбранного процесса, при условии, что операция записи доступна.
Аргументы
- hProcess - дескриптор процесса, память которого будет модифицироваться. Дескриптор должен иметь доступ PROCESS_VM_WRITE и PROCESS_VM_OPERATION к процессу.
- lpBaseAddress – Указатель на базовый адрес в выбранном процессе, куда будет осуще-ствлена запись данных.
- lpBuffer – Указатель на буфер содержащий информацию, которая будет записано в ад-ресное пространство выбранного процесса.
- nSize – Количество байт, которое будет записано в указанный процесс
- lpNumberOfBytesWritten – Указатель на переменную, содержащую количество байт, ко-торые были переданы указанному процессу. Данный аргумент опционален и может быть пустым (NULL).
Возвращаемые значения
- Если функция завершается успешно, величина возвращаемого значения - не нуль.
- Если функция завершается с ошибкой, величина возвращаемого значения - нуль. Чтобы получить дополнительные данные об ошибке, вызовите GetLastError.
4. GetProcAddress(GetModuleHandle('KERNEL32.DLL'), PAnsiChar('LoadLibraryA'));
Code:
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
Функция GetProcAddress извлекает адрес экспортируемой функции или переменной из задан-ной динамически подключаемой библиотеки (DLL).
Аргументы
- hModule - дескриптор модуля DLL, который содержит функцию или переменную. Функ-ция LoadLibrary или GetModuleHandle возвращает этот дескриптор.
- lpProcName - указатель на символьную строку с нулем в конце, которая определяет функцию или имя переменной, или порядковое значение функции. Если этот параметр - порядковое значение, оно должно находиться в младшем слове; старшее слово - должно быть нуль.
Возвращаемые значения
- Если функция завершается успешно, возвращаемое значение - адрес экспортируемой функции или переменной.
- Если функция завершается ошибкой, возвращаемое значение - ПУСТО (NULL). Чтобы получить дополнительную информацию об ошибке, вызовите GetLastError.
5. CreateRemoteThread(Hdl, nil, 0, pThreadStart, ParamAddr, 0,hRemoteThread);
Code:
HANDLE CreateRemoteThread(
HANDLE hProcess, // хендл процесса, в котором создаёт-ся поток
LPSECURITY_ATTRIBUTES lpThreadAttributes, //атрибуты безопасности
DWORD dwStackSize, //размер стека
LPTHREAD_START_ROUTINE lpStartAddress, //адрес функции потока
LPVOID lpParameter, // параметр для функции
DWORD dwCreationFlags, // флаги создания
LPDWORD lpThreadId // указатель на переменную, в которой будет сохранён ID потока
);
Функция CreateRemoteThread создает поток, который выполняется в виртуальном адресном пространстве другого процесса.
Аргументы
- hProcess - Дескриптор процесса, в котором поток должен быть создан. Дескриптор дол-жен иметь права доступа PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE и PROCESS_VM_READ
- lpThreadAttributes - указатель на структуру SECURITY_ATTRIBUTES, которая обуславли-вает, может ли возвращенный дескриптор быть унаследован дочерними процессами. Ес-ли lpThreadAttributes является значением ПУСТО (NULL), дескриптор не может быть унаследован.
- dwStackSize - начальный размер стека, в байтах. Система округляет это значение до са-мой близкой страницы памяти. Если это значение нулевое, новый поток использует по умолчанию размер стека исполняемой программы.
- lpStartAddress - указатель на определяемую программой функцию типа LPTHREAD_START_ROUTINE, код которой исполняется потоком и обозначает начальный адрес потока.
- lpParameter - указатель на переменную, которая передается в поток.
- dwCreationFlags - флажки, которые управляют созданием потока. Если установлен фла-жок CREATE_SUSPENDED, создается поток в состоянии ожидания и не запускается до тех пор, пока не будет вызвана функция ResumeThread. Если это значение нулевое, по-ток запускается немедленно после создания. В настоящее время, никакие другие значе-ния не поддерживаются.
- lpThreadId - указатель на переменную, которая принимает идентификатор потока.
Возвращаемые значения
- Если функция завершается успешно, величина возвращаемого значения - дескриптор нового потока.
- Если функция завершается ошибкой, величина возвращаемого значения - ПУСТО (NULL). Чтобы получить дополнительные данные об ошибках, вызовите GetLastError.
6. WaitForSingleObject(hThread, INFINITE);
Code:
DWORD WaitForSingleObject (
HANDLE hHandle,
DWORD dwMilliseconds
);
Функция WaitForSingleObject возвращает свое значение в двух случаях: когда указанный объ-ект устанавливается в отмеченное состояние; когда истекает время ожидания. Данная функция проверяет текущее состояние указанного объекта. Если данный объект находится в неотме-ченном состоянии, выполнение потока приостанавливается. В процессе ожидания поток прак-тически не использует процессорное время. Перед завершением своей работы функция изме-няет состояние некоторых объектов синхронизации. Изменения происходят только в том слу-чае, если изменение состояния объекта привело к выходу из функции. Например, счетчик се-мафора уменьшается на единицу. Функция WaitForSingleObject может использоваться со сле-дующими объектами:
- извещениями об изменениях;
- вводом с системной консоли;
- объектами событий;
- заданиями;
- мютексами;
- процессами;
- семафорами;
- потоками;
- таймерами ожидания.
Необходимо соблюдать известную осторожность при вызове функций ожидания и программ, прямо или косвенно создающих объекты окон. Если поток создает любое окно, оно должно об-рабатывать сообщения. Сообщения посылаются всем окнам системы. Поток, использующий функцию ожидания без интервала ожидания, может "подвесить" систему.
Аргументы
- hHandle - дескриптор объекта. Список типов объектов, дескрипторы которых могут ис-пользоваться в качестве данного аргумента, содержится в примечании. В Windows NT дескриптор должен иметь уровень доступа SYNCHRONIZE.
- dwMilliseconds - определяет интервал времени, измеряемый в миллисекундах. По исте-чении этого интервала времени, если указанный объект остается неотмеченным, функ-ция завершает свою работу. Если данный аргумент имеет нулевое значение, функция проверяет состояние объекта и немедленно прекращает свою работу. Если аргумент dwMilliseconds имеет значение INFINITE, то функция ждет отметки объекта неограничен-ное время.
Возвращаемые значения
Если функция успешно завершает свою работу, то возвращаемое значение указывает на при-чину завершения работы функции. Оно может принимать одно из следующих значений:
- WAIT_ABANDONED - указанный объект является мютексом, который не был освобожден потоком, которому он принадлежит, перед завершением данного потока. Принадлеж-ность мютекса вызывающему потоку гарантируется. Поэтому этот объект остался неот-меченным;
- WAIT_OBJECT_0 -указанный объект находится в отмеченном состоянии;
- WAIT_TIMEOUT - истек период ожидания, а объект остался неотмеченным.
Если функция аварийно завершает свою работу, она возвращает значение WAIT_FAILED. Более подробную информацию об ошибке можно получить, вызвав функцию GetLastError.
7. Closehandle(hThread);
Code:
BOOL CloseHandle (
HANDLE hHandle
);
Функция CloseHandle аннулирует заданный дескриптор объекта, уменьшает итоговое число де-скрипторов объекта и выполняет проверку наличия объекта. После того, как последний деск-риптор объект закрывается, объект удаляется из системы.
Аргументы
- hHandle - дескриптор открытого объекта.
Возвращаемые значения
- Если функция завершается успешно, величина возвращаемого значения - не нуль.
- Если функция завершается с ошибкой, величина возвращаемого значения - нуль. Чтобы получить дополнительные данные об ошибке, вызовите GetLastError.