• 0x01 - Intro
  • 0x02 - APIs
  • 0x03 - Inyectando con DLL
  • 0x04 - Inyectando sin DLL
  • 0x05 - Referencias

0×01 | Intro

Voy a explicar mas o menos como inyectar nuestro propio código en otro proceso. Primero inyectaremos una dll, después lo haremos a pelo.

0×02 | API’s

Primero necesitamos conocer algunas APIs de windows. Si ya las conoces, o sólo te interesa el método, salta a la siguiente sección.

OpenProcess:

HANDLE WINAPI OpenProcess(
	__in DWORD dwDesiredAccess,
	__in BOOL bInheritHandle,
	__in DWORD dwProcessId
);

Abre el proceso especificando su PID en dwProcessId, con los privilegios deseados. Retorna un manejador al proceso, o NULL en caso de error.

VirtualAllocEx:

LPVOID WINAPI VirtualAllocEx(
	__in HANDLE hProcess,
	__in_opt LPVOID lpAddress,
	__in SIZE_T dwSize,
	__in DWORD flAllocationType,
	__in DWORD flProtect
);

Reserva memoria en el espacio de otro proceso, comenzando en lpAddress (si se le pasa NULL el sistema elige por nosotros), con el tamaño dwSize y con los permisos que le asignemos (usaremos PAGE_EXECUTE_READWRITE). Devuelve un puntero a esa memoriam o NULL en caso de error.

WriteProcessMemory:

BOOL WINAPI WriteProcessMemory(
	__in HANDLE hProcess,
	__in LPVOID lpBaseAddress,
	__in LPCVOID lpBuffer,
	__in SIZE_T nSize,
	__out SIZE_T *lpNumberOfBytesWritten
);

Escribe en la memoria dada por lpBaseAddress el contenido apuntado por lpBuffer, hasta nSize bytes. Devuelve FALSE en caso de error.

CreateRemoteThread:

HANDLE WINAPI CreateRemoteThread(
	__in HANDLE hProcess,
	__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
	__in SIZE_T dwStackSize,
	__in LPTHREAD_START_ROUTINE lpStartAddress,
	__in LPVOID lpParameter,
	__in DWORD dwCreationFlags,
	__out LPDWORD lpThreadId
);

Lanza un hilo en el proceso hProcess (abierto con OpenProcess), estando en ese hilo la funcion apuntada por lpStartAddress con los argumentos apuntados por lpParameter.

También usaremos Tlhelp32.h para obtener el PID de un proceso sabiendo su nombre.


HANDLE processList=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pInfo;
BOOL st=TRUE;
pInfo.dwSize=sizeof(PROCESSENTRY32);
Process32First(processList, &pInfo);
int myPid=0;
do
{
	if(strcmp(pInfo.szExeFile, "test.exe")==0)
	{
		myPid=pInfo.th32ProcessID;
		break;
	}
	Process32Next(lista, &pInfo);
}
while(st!=FALSE);

Ver MSDN para mas info.

0×03 | Inyectando con DLL

Primero necesitamos una DLL que inyectar, la cual al ser programada por nosotros hará lo que queramos.


#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

BOOL APIENTRY DllMain (HINSTANCE hInst,
DWORD reason,
LPVOID reserved)
{
	switch (reason)
	{
		case DLL_PROCESS_ATTACH:
		MessageBoxA(NULL, "Hi!", "Hey!", 0);
		break;
	}
	return TRUE;
}

Ahora tenemos que hacer al proceso cargar esa DLL. Para ello: - Abrimos el proceso, reservamos memoria en él, y escribimos en esa memoria la ruta de la DLL. - Obtener la dirección de LoadLibrary con GetProcAddress. LoadLibrary se encuentra en kernel32.dll, y su dirección es compartida, por lo que podemos cargarla desde el inyector y usarla desde el inyectado. - Lanzar un hilo en el proceso abierto, dando como punto de entrada la dirección de LoadLibrary y como argumento la dirección del nombre de la DLL previamente escrito.

Todo junto queda así:


#include <stdio.h>
#include <windows.h>
#include <Tlhelp32.h>

void error(char *err);

HANDLE myProc=NULL;

int main(int argc, char *argv[])
{
	HANDLE processList=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pInfo;
	BOOL st=TRUE;
	pInfo.dwSize=sizeof(PROCESSENTRY32);
	Process32First(processList, &pInfo);
	int myPid=0;
	do
	{
		if(strcmp(pInfo.szExeFile, "test.exe")==0)
		{
			myPid=pInfo.th32ProcessID;
			break;
		}
		Process32Next(processList, &pInfo);
	}
	while(st!=FALSE);

	// Abrir el proceso
	printf("[+] Opening process %i\n", myPid);
	myProc=OpenProcess(PROCESS_ALL_ACCESS, FALSE, myPid);
	if(myProc==NULL) error("[-] Error abriendo proceso.\n");
	else printf("[+] Proceso abierto.\n");

	// Reservar memoria para el argumento (ruta de la DLL)
	char thData[]="dll.dll";
	LPVOID dirToArg=VirtualAllocEx(myProc, NULL, strlen(thData), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(dirToArg==NULL) 
		error("[-] Error reservando memoria para argumento.\n");
	else 
		printf("[+] Memoria reservada para argumento (%i bytes).\n", strlen(thData));
	// Escribir la ruta de la DLL en la memoria reservada
	SIZE_T written=0;
	if(WriteProcessMemory(myProc, dirToArg, (LPVOID)&thData, strlen(thData), &written)==0) 
		error("[-] Error escribiendo memoria.\n");
	else 
		printf("[+] Memoria escrita (arg %i bytes).\n", written);

	// Lanzar un hilo con LoadLibrary
	HANDLE rThread=CreateRemoteThread(myProc, NULL, 0, 
						(LPTHREAD_START_ROUTINE)GetProcAddress(LoadLibrary("Kernel32.dll"), "LoadLibraryA"), 
						dirToArg, 0, NULL);
	if(rThread==NULL) 
		error("[-] Error creando el hilo.\n");
	else printf("[+] Hilo creado.\n");

	CloseHandle(myProc);
}

void error(char *err)
{
	if(myProc!=NULL) CloseHandle(myProc);
	printf("%s", err);
	exit(0);
}

0×04 | Inyectando sin DLL

Aquí la cosa se complica. Las diferencias entre un método y otro son: - Ahora no podemos utilizar cadenas de texto directamente como argumentos a funciones. Ya veremos por qué. - No podemos llamar a ninguna función que no carguemos previamente con LoadLibrary + GetProcAddress. Esas dos están cargadas en todos los ejecutables de Windows desde kernel32, así que podemos usar un puntero a ellas. - Tenemos que escribir todo el código de nuestras funciones en el espacio de memoria del proceso. - Necesitaremos estructuras mas complejas como argumento de nuestros hilos remotos. Así que el proceso de inyección quedaría así: - Crear una estructura de datos con TODAS las cadenas de texto que vayamos a usar en el código inyectado, y con un puntero a LoadLibrary y GetProcAddress. - Abrir el proceso. - Reservar memoria para la estructura de datos. Escribirla. - Reservar memoria para nuestro código. Escribirlo. - Lanzar el hilo remoto, dando como punto de entrada el puntero a neustro código y como argumento el puntero a nuestra estructura de datos. Y el código queda así (en el ejemplo inyecto un MessageBoxA, cargado desde User32.dll)


#include <stdio.h>
#include <windows.h>
#include <Tlhelp32.h>

void error(char *err);
static DWORD WINAPI myFunc(LPVOID data);

HANDLE myProc=NULL;

// Con esto cargaremos punteros a LoadLibrary y GetProcAddress en nuestra estrucura de datos
typedef int (WINAPI *datLoadLibrary)(LPCTSTR);
typedef int (WINAPI *datGetProcAddress)(HMODULE, LPCSTR);

struct tdata {
	char lnUser32[50]; // -> "User32.dll"
	char fnMessageBoxA[50]; // -> "MessageBoxA"
	datLoadLibrary apiLoadLibrary; // Puntero a LoadLibrary
	datGetProcAddress apiGetProcAddress; // Puntero a GetProcAddress
	char Msg[50]; // Texto que usaremos en MessageBoxA
};

int main(int argc, char *argv[])
{
	if(argc<2) 
		error("Uso: hook.exe PROCESO\n");
	// Esta es nuestra estructura de argumentos
	struct tdata thData;
	
	strcpy(thData.lnUser32, "User32.dll");
	strcpy(thData.fnMessageBoxA, "MessageBoxA");
	strcpy(thData.Msg, "Hola!");
	thData.apiLoadLibrary=GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	thData.apiGetProcAddress=GetProcAddress(GetModuleHandle("kernel32.dll"), "GetProcAddress");

	int funcSize=600; 	// El tamaño de nuestra función. Podria calcularse de forma exacta, pero como ejemplo
						// exagerado nos vale .

	HANDLE processList=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pInfo;
	BOOL st=TRUE;
	pInfo.dwSize=sizeof(PROCESSENTRY32);
	Process32First(processList, &pInfo);
	int myPid=0;
	do
	{
		if(strcmp(pInfo.szExeFile, argv[1])==0)
		{
			myPid=pInfo.th32ProcessID;
			break;
		}
		Process32Next(processList, &pInfo);
	}
	while(st!=FALSE);

	// Abrir proceso
	printf("[+] Abriendo proceso %i\n", myPid);
	myProc=OpenProcess(PROCESS_ALL_ACCESS, FALSE, myPid);
	if(myProc==NULL) 
		error("[-] Error abriendo proceso.\n");
	else 
		printf("[+] Proceso abierto.\n");

	// Reservar memoria para argumentos
	LPVOID dirToArg=VirtualAllocEx(myProc, NULL, sizeof(thData), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(dirToArg==NULL) 
		error("[-] Error reservando memoria para arg.\n");
	else 
		printf("[+] Memoria reservada para arg (%i bytes).\n", sizeof(thData));
	// Escribir argumentos
	SIZE_T written=0;
	if(WriteProcessMemory(myProc, dirToArg, (LPVOID)&thData, sizeof(thData), &written)==0) 
		error("[-] Error escribiendo la estructura de datos.\n");
	else 
		printf("[+] Memoria escrita (arg %i bytes).\n", written);

	// Reservar memoria para la funcion
	LPVOID dirToWrite=VirtualAllocEx(myProc, NULL, funcSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if(dirToWrite==NULL) 
		error("[-] Error rreservando memoria para codigo.\n");
	else 
		printf("[+] Memoria reservada para codigo (%i bytes).\n", funcSize);
		
	// Escribimos el codigo de nuestra funcion
	if(WriteProcessMemory(myProc, dirToWrite, (LPVOID)myFunc, funcSize, &written) == 0) 
		error("[-]Error escribiendo memoria.\n");
	else 
		printf("[+] Memoria escrita (codigo).\n");

	// Lanzamos el hilo
	HANDLE rThread=CreateRemoteThread(myProc, NULL, 0, (LPTHREAD_START_ROUTINE)dirToWrite, dirToArg, 0, NULL);
	if(rThread==NULL) 
		error("[-] Error lanzando hilo.\n");
	else 
		printf("[+] Hilo lanzado.\n");
	
	CloseHandle(myProc);
	return 0;
}

void error(char *err)
{
	if(myProc!=NULL) CloseHandle(myProc);
	printf("%s", err);
	exit(0);
}

static DWORD WINAPI myFunc(LPVOID data)
{
	// Cargamos nuestros datos en una estructura como la que hicimos
	struct tdata *thData;
	thData=data;
	// Podemos conseguir cualquier API con estas dos, siempre que tengamos su nombre en la estructura
	void *apiDir=(void *)thData->apiGetProcAddress((HANDLE)thData->apiLoadLibrary(thData->lnUser32), thData->fnMessageBoxA);
	// Puntero a funcion similar a MessageBoxA
	INT WINAPI (*myMessageBox)(HWND, LPCSTR, LPCSTR, UINT);
	myMessageBox=apiDir;
	myMessageBox(NULL, thData->MSG, thData->MSG, 0);
	return;
}

Creo que todo va bien explicado. Si he metido la gamba en algo (cosa muy posible), contádmelo y tal, en un comentario.