Внедрение в чужой процесс
В этой статье я расскажу немного про память процессов, а так же покажу простейший способ внедрения своего кода в работающую программу, например explorer.exe.
Откроем в отладчике любую программу под Windows и рассмотрим диапазон адресов, в которых она находится. Обычно этот диапазон расположен в адресах 0х0400000 – 0х04ххххх (но, может быть и другим, в частности для системных процессов). Откроем другую программу, она располагается в том же самом диапазоне адресов. У мало знакомого с оперативной памятью человека возникнет логичный вопрос: как возможно, что бы разные программы занимали одни и те же же адреса? Дело вот в чем: для каждой запущенной программы ОС выделяет свое виртуальное адресное пространство, которое начинающееся с адреса 00000000h и заканчивается адресом FFFFFFFFh, размер равен 4 Гб (мы говорим про 32-битные системы). Тут стоит отметить, что 4Гб виртуальной памяти это не тоже что и 4Гб памяти физической. Об этом чуть подробнее:
Виртуальная память разбита на страницы (размер страницы определяется типом ОС, в нашем случае это 4 кб). При запуске программы эти страницы выделены под сам исполняемый код, данные нашего приложения, стек и т.д. Эти страницы виртуальной памяти уже потом проецируются на физическую память. Подробнее про это лучше почитать у Джеффри Рихтера (у него про работу с памятью есть практически все). Если мы откроем диспетчер задач и посмотрим закладку «процессы», то увидим все процессы, которые запущены в данный момент. Они располагаются в своих виртуальных адресных пространствах (ВАП).
ВАП каждого процесса (каждой программы) изолировано, т.е. не затрагивает ВАП другого процесса, и управляется оно функциями ядра операционной системы. На деле же при необходимости, память другого процесса можно читать и редактировать. Что нам даст внедрение в процесс (process injection)? В отличии от работы с памятью средствами своей программы, внедренный процесс не будет виден ни в диспетчере задач, ни в любой другой программе. Такое глобальное внедрение позволит осуществить перехват вызовов API функций или, например, внедрение в браузер для обхода файрвола. Суть метода заключается в выделении области памяти строго заданного размера в чужом процессе, копировании туда побайтно кода нашего процесса по тем же адресам и запуске удаленного потока.
Реализацию можно разбить на составляющие:
* подбираем программу, в которую будем внедряться
* выделяем в ее адресном пространстве память для нашего процесса
* копируем код нашего процесса в выделенную область
* осуществляем запуск удаленного потока
Функция для поиска процесса принимает в параметрах строку, в которой содержится название искомого процесса (например explorer.exe) и возвращает его ID (уникальный идентификатор):
Code:
DWORD GetProcessID(char* lpNameProcess)
{
HANDLE snap;
PROCESSENTRY32 pentry32;
snap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(snap==INVALID_HANDLE_VALUE) return 0;
pentry32.dwSize=sizeof(PROCESSENTRY32);
if(!Process32First(snap,&pentry32)) {CloseHandle(snap);return 0;}
do
{
if(!lstrcmpi(lpNameProcess,&pentry32.szExeFile[0]))
{
CloseHandle(snap);
return pentry32.th32ProcessID;
}
}while(Process32Next(snap,&pentry32));
CloseHandle(snap);
return 0;
}
Далее зарезервируем в чужом пространстве область памяти, необходимую для размещения нашего кода. Сразу возникает вопрос: сколько памяти выделять? Необходимый размер лучше определить программно, как продемонстрировано ниже. В качестве первого параметра наша функция принимает Handle чужого процесса, в качестве второго – указатель на функцию, которая будет запущена в чужом адресном пространстве (точка входа в поток). В переменной size вычисляется размер области памяти (в которую мы будем копировать код). Получив величину size, с помощью функции VirtualAllocEx мы выделяем в адресном пространстве чужого процесса необходимую нам область памяти. Сама операция копирования осуществляется функцией WriteProcessMemory:
Code:
BOOL Inject(HANDLE hProc,DWORD(WINAPI* func)(LPVOID))
{
DWORD id;
DWORD ByteOfWriten;
HMODULE hModule = GetModuleHandle(NULL);
DWORD size=((PIMAGE_OPTIONAL_HEADER)((LPVOID)((BYTE*)(hModule)+((PIMAGE_DOS_HEADER)(hModule))->e_lfanew+sizeof(DWORD)+sizeof(IMAGE_FILE_HEADER))))->SizeOfImage;
char* hNewModule = (char*)VirtualAllocEx(hProc,hModule,size, MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(hNewModule==NULL) return false;
WriteProcessMemory(hProc,hNewModule,hModule,size,&ByteOfWriten);
if(ByteOfWriten!=size){return false;}
HANDLE hThread=CreateRemoteThread(hProc,NULL,0,func,(LPVOID)hNewModule,0,&id);
if(hThread==0) return false;
return true;
}
Надо отметить, что копирование может не удаться, если в пространстве explorer.exe уже содержатся его данные по нашим адресам памяти. Чтобы избежать этого, сдвигаем образ нашего процесса максимально дальше с помощью директивы компилятора:
Code:
#pragma comment(linker,"/BASE:0x13140000")
Далее производим запуск удаленного потока (содержащего нашу функцию) с помощью функции CreateRemoteThread с точки входа func. func — именно та функция, которая будет работать в чужом адресном пространстве. Возьмем простейший вариант с выводом окошка с сообщением, вот полный код:
Code:
#include <windows.h>
#include <tlhelp32.h>
#pragma comment(linker,"/BASE:0x13140000") // сдвигаем базу нашего процесса
// ------- объявляем функции --------
DWORD GetProcessID(char*);
BOOL Inject(HANDLE,DWORD(WINAPI* func)(LPVOID));
DWORD WINAPI func(LPVOID);
//-------- главная функция ---------
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPTSTR,int)
{
if(!Inject(OpenProcess(PROCESS_ALL_ACCESS,false,GetProcessID("explorer.exe")),&func)) return false;
return true;
}
//-------- функция, которая будет выполняться в чужом процессе -------
DWORD WINAPI func(LPVOID)
{
LoadLibrary("kernel32.dll");
LoadLibrary("user32.dll");
MessageBox(0,"Hello from addres area of explorer","title",0);
return true;
}
//-------- поиск процесса ---------
DWORD GetProcessID(char* lpNameProcess) // в параметре - имя процесса для внедрения
{
HANDLE snap;
PROCESSENTRY32 pentry32;
snap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(snap==INVALID_HANDLE_VALUE) return 0;
pentry32.dwSize=sizeof(PROCESSENTRY32);
if(!Process32First(snap,&pentry32)) {CloseHandle(snap);return 0;}
do
{
if(!lstrcmpi(lpNameProcess,&pentry32.szExeFile[0]))
{
CloseHandle(snap);
return pentry32.th32ProcessID;
}
}
while(Process32Next(snap,&pentry32));
CloseHandle(snap);
return 0;
}
//-------- функция внедрения в чужой процесс -------------------
BOOL Inject(HANDLE hProc,DWORD(WINAPI* func)(LPVOID))
{
DWORD id;
DWORD ByteOfWriten;
HMODULE hModule = GetModuleHandle(NULL);
DWORD size=((PIMAGE_OPTIONAL_HEADER)((LPVOID)((BYTE*)(hModule)+((PIMAGE_DOS_HEADER)(hModule))->e_lfanew+sizeof(DWORD)+sizeof(IMAGE_FILE_HEADER))))->SizeOfImage;
char* hNewModule = (char*)VirtualAllocEx(hProc,hModule,size,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(hNewModule==NULL) return false;
WriteProcessMemory(hProc,hNewModule,hModule,size,&ByteOfWriten);
if(ByteOfWriten!=size){return false;}
HANDLE hThread=CreateRemoteThread(hProc,NULL,0,func,(LPVOID)hNewModule,0,&id);
if(hThread==0) return false;
return true;
}
(C) veveve