Салют, друзья! На дворе 2018 год, а мы с вами начинаем изучать DLL инъекции . Не смотря на то, что эта техника уже много раз заезжена и всем известна, я всё же внесу свои 5 копеек, так как в будующих статьях мы будем частенько прибегать к этой технике и я просто не мог пропустить её.
DLL Injection
Что же за DLL-инъекции, то такие? Для начала, нужно рассказать, что такое DLL.
DLL (Dynamic Link Library) - исполняемый модуль, в котором хранятся функции, используемые программой. Да! Их просто вынесли в отдельный исполняемый модуль. DLL'ки загружаются программой, а после этого используются функции, которые экспортирует DLL. Пример DLL - kernel32.dll в нём хранятся основные функции Windows API.
При загрузке/выгрузке DLL вызывается определённая функция этой DLL. Её обычно называют инициализирующей и по умолчанию она носит имя DllMain.
Вернёмся к нашим баранам, а точнее DLL инъекциям. На самом деле, DLL инъекция - это один из способов выполнения стороннего кода в контексте другого процесса. Говоря проще, это техника внедрения кода в уже загруженный произвольный исполняемый файл. Зачем она нужна? Ну, рассмотрим самые частые случаи использования DLL:
- Иногда нам может понадобиться внедрить в процесс собственный код (функцию) (для использования самой программой, для перехват API, для перехвата других функций и т.д.).
- Иногда очень важно получить прямой доступ к памяти нужного нам процесса (для модифицирования содержимого памяти либо чтения), не смотря на то что существуют WriteProcessMemory и ReadProcessMemory WinApi функции.
- Скрытие вредоносного кода в легитимном процессе.
- и т.д. и т.п.
Теория
Основная идея DLL инъекции - загрузить DLL. Для этого может использоваться огромное количество способов, вплоть до ручной загрузки DLL (этот способ называется Reflected DLL Injection). Но как я сказал выше, мы рассмотрим самый быстрый и простой способ - загрузку с помощью LoadLibrary через удалённый поток.
В принципе, процесс внедрения DLL можно разделить на несколько шагов:
1. Открываем процесс (получаем доступ к целевому процессу) с помощью WinApi функции OpenProcess.
Ссылка скрыта от гостей
. Вам лучше запомнить её, так как мы часто будем использовать её в наших программах. Проще говоря, нам нужно получить доступ к процессу.2. Выделяем память для имени DLL, которую мы хотим внедрить.
Ссылка скрыта от гостей
.3. Записываем путь к DLL по выделенной памяти
Ссылка скрыта от гостей
.4. Запускаем LoadLibrary
Вся магия DLL инъекций скрыта как-раз таки на этом этапе, а предыдущие шаги были подготовительными. В данном этапе, мы вызываем функцию
Ссылка скрыта от гостей
, которая и загружает нашу DLL'ку.После всех наших действий, автоматически запускается инциализирующая функция нашей DLL, которая выполняет свою
Практика
Для закрепления теории, предлагаю написать простой консольный DLL инжектор, который загрузит нужную нам DLL в нужный нам процесс.
Давайте напишем функцию, с помощью которой мы получим PID используя имя процесса. Здесь мы получаем список всех процессов, а потом проходясь по этому списку, сравниваем имя процесса в списке с именем нужного нам процесса. Если мы нашли процесс, то возвращает его PID.
C++:
DWORD GetPidByProcessName(LPTSTR lpszProcessName)
{
HANDLE hSnapshot;
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
throw std::exception("Can't create the snapshot!");
}
if (Process32First(hSnapshot, &pe32))
{
do
{
if (_tcscmp(pe32.szExeFile, lpszProcessName) == 0)
{
return pe32.th32ProcessID;
}
}
while (Process32Next(hSnapshot, &pe32));
}
throw std::exception("Can't find the process!");
}
Для этой функции нам понадобиться подключить два заголовочных файла:
C++:
#include <Windows.h>
#include <TlHelp32.h>
Ну и объявим две константы. Одну для имени DLL, другую для имени процесса (инъектить DLL'ку мы будем в наш замученный hello.exe).
C++:
#define TARGET_PROCESS "hello.exe"
#define TARGET_DLL "D:\\test.dll"
Теперь, добавим следующий код в главную функцию. Здесь мы получаем PID процесса и получаем DLL, а также размер строки с путём до DLL.
C++:
char sDLLName[] = TARGET_DLL;
DWORD dwSize = sizeof(sDLLName)+1;
DWORD dwProcessID;
try
{
dwProcessID = GetPidByProcessName(_TEXT(TARGET_PROCESS));
}
catch (std::exception& e)
{
std::wcout << "[ERROR] " << e.what() << std::endl;
getchar();
return 1;
}
std::wcout << "[INFO] ProcessID = " << dwProcessID << std::endl;
Так, мы получили PID процесса. А для чего же он нам нужен? Конечно же, чтобы открыть процесс! (весь последующий код добавляйте в функцию main (или _tmain) прим. автора). Первым пунктом инъекции у нас идёт открытие процесса. Взглянем на функцию открытия процесса поподробнее.
Протопип функции:
C++:
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
- dwDesiredAccess
Права доступа, которые мы хотим получить открыв процесс. Установим PROCESS_ALL_ACCESS, чтобы получить все права на процесс. - bInheritHandle
Флаг наследования дескриптора. Установим его в FALSE. - dwProcessId
Идентификатор процесса (Process ID или PID)
C++:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcess == NULL)
{
std::wcout << "[ERROR] Can't open the target process!" << std::endl;
CloseHandle(hProcess);
getchar();
return 1;
}
Что нам нужно сделать после открытия процесса? Правильно! Выделить память для пути к DLL. Взглянем на функцию VirtualAllocEx поподробнее, ведь именно с помощью неё мы и выделим память!
C++:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
- hProcess
Дескриптор процесса, в котором мы хотим выделить память - lpAddress
Адрес, по которому хотим выделить память. Если он нам не важен, то мы можем установить NULL. - dwSize
Размер выделяемой памяти. - flAllocationType
Тип выделения - flProtect
Права на выделенную память.
C++:
LPVOID lpDllName = VirtualAllocEx(hProcess, NULL, dwSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpDllName == NULL)
{
std::wcout << "[ERROR] Can't allocate memory in the target process!"
<< std::endl;
CloseHandle(hProcess);
getchar();
return 1;
}
std::wcout << "[INFO] Memory allocated at 0x" << lpDllName << std::endl;
Зачем выделять? Чтобы записать сюда имя DLL. Изучим функцию WriteProcessMemory, с помощью которой мы и запишем имя DLL'ки в нужный нам процесс по нужному нам адресу памяти.
Код:
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
- hProcess
Дескриптор процесса, в память которого мы и хотим записать нужные нам данные. - lpBaseAddress
Адрес в памяти процесса, по которому мы хотим записать данные. - lpBuffer
Указатель на буфер, который мы хотим записать. - nSize
Размер буфера (или сколько байтов нам нужно записать). - lpNumberOfBytesWritten
Указатель на целочисленную переменную, в которую будет записано число записанных байт.
Перейдём к коду. Здесь мы запишем имя DLL'ки и проверим на ошибки.
C++:
BOOL bRes = WriteProcessMemory(hProcess, lpDllName, sDLLName,
dwSize, NULL);
if (!bRes)
{
std::wcout << "[ERROR] Can't write the dll name in the target process!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
Зачем записывать? Чтобы загрузить DLL, конечно. Но перед этим, нам нужно получить адрес функции LoadLibraryA (с помощью двух функций, GetModuleHandle и GetProcAddress), которая и загрузит нашу DLL'ку, а уже потом создать сам удалённый поток. Что такое удалённый поток? Проще говоря, с помощью удалённого потока мы можем вызвать любую нужную нам функцию в нужном нам процессе передав нужные нам параметры.
C++:
LPVOID lpLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
if (lpLoadLibrary == NULL)
{
std::wcout << "[ERROR] Can't get the LoadLibraryA address!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
LPTHREAD_START_ROUTINE(lpLoadLibrary), lpDllName, 0, 0);
if (hThread == NULL)
{
std::wcout << "[ERROR] Can't create remote thread!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
Ну-с, в принципе, на этом процесс инъекции и заканчивается. Добавим финальные штрихи:
C++:
std::wcout << "[+] DLL Injected!" << std::endl;
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 0;
Финальный код выглядит так:
C++:
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#define TARGET_PROCESS "hello.exe"
#define TARGET_DLL "D:\\test.dll"
DWORD GetPidByProcessName(LPTSTR lpszProcessName)
{
HANDLE hSnapshot;
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
throw std::exception("Can't create the snapshot!");
}
if (Process32First(hSnapshot, &pe32))
{
do
{
if (_tcscmp(pe32.szExeFile, lpszProcessName) == 0)
{
return pe32.th32ProcessID;
}
}
while (Process32Next(hSnapshot, &pe32));
}
throw std::exception("Can't find the process!");
}
int _tmain(int argc, _TCHAR* argv[])
{
char sDLLName[] = TARGET_DLL;
DWORD dwSize = sizeof(sDLLName)+1;
DWORD dwProcessID;
try
{
dwProcessID = GetPidByProcessName(_TEXT(TARGET_PROCESS));
}
catch (std::exception& e)
{
std::wcout << "[ERROR] " << e.what() << std::endl;
getchar();
return 1;
}
std::wcout << "[INFO] ProcessID = " << dwProcessID << std::endl;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcess == NULL)
{
std::wcout << "[ERROR] Can't open the target process!" << std::endl;
CloseHandle(hProcess);
getchar();
return 1;
}
LPVOID lpDllName = VirtualAllocEx(hProcess, NULL, dwSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpDllName == NULL)
{
std::wcout << "[ERROR] Can't allocate memory in the target process!"
<< std::endl;
CloseHandle(hProcess);
getchar();
return 1;
}
std::wcout << "[INFO] Memory allocated at 0x" << lpDllName << std::endl;
BOOL bRes = WriteProcessMemory(hProcess, lpDllName, sDLLName,
dwSize, NULL);
if (!bRes)
{
std::wcout << "[ERROR] Can't write the dll name in the target process!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
HMODULE hKernel32 = GetModuleHandle(_TEXT("kernel32.dll"));
if (hKernel32 == NULL)
{
std::wcout << "[ERROR] Can't get the handle of kernel32.dll!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
LPVOID lpLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
if (lpLoadLibrary == NULL)
{
std::wcout << "[ERROR] Can't get the LoadLibraryA address!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
LPTHREAD_START_ROUTINE(lpLoadLibrary), lpDllName, 0, 0);
if (hThread == NULL)
{
std::wcout << "[ERROR] Can't create remote thread!"
<< std::endl;
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 1;
}
std::wcout << "[+] DLL Injected!" << std::endl;
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpDllName, dwSize, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return 0;
}
Но что мы будем внедрять? А точнее, какую DLL мы будем внедрять? Для этого, я создал простую DLL с именем test.dll и поместил его на диск D. Вот код DLL'ки:
C++:
#include <windows.h>
extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "DLL Injected!", "DLL", MB_OK | MB_ICONINFORMATION);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Скомпилируем и протестируем!
Последнее редактирование: