Ahora le toca el turno a la función inyectora. Si os fijais, esta función está declarada como si fuese la función que se inyecta, es decir recibe los parápetros que desearíamos pasar al código inyectado y nos devuelve el resultado de la inyección. Realmente esta función es un interface entre nuestro proceso y el proceso en el que nos vamos a inyectar. Veamos como funciona:
// Función que inyecta y ejecuta InjFreeLibrary
// ¡¡¡ No cambiar de sitio !!!
BOOL FarFreeLibraryST(DWORD Pid, char *DllName)
{
POpenThread OpenThread;
HANDLE hProcess; // El handle del proceso en el que inyectaremos
HANDLE hThread; // El handle del thread principal del proceso
CONTEXT Context;
DWORD Tid = 0;
DWORD OldProtect;
TParamFL Param; // El tipo de dato de la estructura
DWORD SizeInject; // Tamaño total a inyectar Parametros + Función
UINT TamFun; // El tamaño de la función a inyectar
void* BFunc; // Lugar de memoria donde copiaremos nuestra función
void* BParam; // Lugar de memoria donde copiaremos los parámetros
void* BShell; // Bloque completo del Shellcode
//Abrimos el proceso en el que nos inyectaremos
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
if(!hProcess) return 0;
//Abrimos el hilo
OpenThread = (POpenThread)GetProcAddress(GetModuleHandle("kernel32.dll"), "OpenThread");
Tid = GetFirstThreadID(Pid);
hThread = OpenThread(THREAD_ALL_ACCESS, false, Tid);
if(!hThread) return 0;
//Metemos la dirección de las apis en la estructura
Param.Result = false;
Param.Terminado = false;
Param.FreeLibrary = (PFreeLibrary)GetProcAddress(GetModuleHandle("KERNEL32.DLL"), "FreeLibrary");
Param.GetModuleHandleA = (PGetModuleHandleA)GetProcAddress(GetModuleHandle("KERNEL32.DLL"), "GetModuleHandleA");
Param.MessageBoxA = (PMessageBoxA)GetProcAddress(GetModuleHandle("user32.dll"), "MessageBoxA");
strcpy(Param.Caption, "Eureca");
strcpy(Param.DllName, DllName);
//Calculamos el tamaño de la función a inyectar y nos damos permisos de escritura...
TamFun = (UINT)FarFreeLibraryST - (UINT)InjFreeLibraryST;
SizeInject = TamFun + sizeof(TParamFL); //Tamaño de la función mas los parámetros.
VirtualProtectEx((void*)-1, InjFreeLibraryST, TamFun, PAGE_EXECUTE_READWRITE, &OldProtect);
//Suspendemos el hilo y tomamos el puntero de instrucciones eip
SuspendThread(hThread);
Context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &Context);
//colocamos el EIP en nuestro código como parte del opcode de la primera instrucción push
*(DWORD*)((DWORD)InjFreeLibraryST+1) = Context.Eip;
//Reservamos memoria y escribimos nuestro código en él
BShell = VirtualAllocEx(hProcess, NULL, SizeInject, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
BFunc = BShell;
BParam = (void*)((DWORD)BShell + TamFun);
WriteProcessMemory(hProcess, BFunc, InjFreeLibraryST, TamFun, NULL); //la función
WriteProcessMemory(hProcess, BParam, &Param, sizeof(TParamFL), NULL); //los parámetros
//Modificamos el eip para que apunte al código inyectado
Context.Eip = (DWORD)BFunc;
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context);
//Le decimos al hilo que puede volver a ejecutarse (lanzará nuestro codigo)
ResumeThread(hThread);
// Provoco la ejecución del thread por parte de algún procesador para que se
// ejecute el Shellcode y devuelva el resultado.
PostThreadMessage(Tid, 0,0,0);
// Leo el bloque de parámetros inyectado para conocer Result
// Debo esperar a que la rutina se ejecute: Param.Terminado == true
do{
ReadProcessMemory(hProcess, BParam, &Param, sizeof(TParamFL), NULL);
}while(!Param.Terminado);
// Limpieza
VirtualFree(BShell, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hThread);
return Param.Result;
}
Esta función deberá encargarse de:
1.- Preparar los parámetros y el código a inyectar para colocar ambos en el espacio de direcciones del proceso "victima".
2.- Localizar un thread seguro, pararlo, realizar la inyección y arrancar nuestro código reanudando el thread congelado.
3.- Comprobar que nuestro código inyectado ha terminado y leer resultados del mismo si los hubiere.
4.- Finalmente deberá realizar las labores de limpieza pertinentes.
El punto 1 es sencillo, fue visto en la primera parte de este tutorial y se basa en el uso de la API
WriteProcessMemory. En este caso colocaremos la estructura de parámetros inmediatamente detrás de la función inyectada, por lo que lo haremos en el punto 2.
El punto 2 debe congelar un thread seguro, para lo que localizamos el primero del proceso a inyectar. Lo paramos, guardamos el EIP en el código binario de la primera instrucción de nuestro código inyectado que será una instrucción push. Con esto aseguramos el retorno. Es ahora cuando concluimos el punto 1 y podemos subir al espacio de direcciones del proceso a inyectar, el código y la estructura de datos. Después cambiamos el EIP para que apunte a nuestro código, ya en el espacio de direcciones del proceso víctima. Seguidamente volvemos a permitir que se ejecute el thread, con lo que se ejecutará nuestro código. Para asegurarnos de que se reanuda la ejecución del thread usaremos la API
PostThreadMessage.
El punto 3 se desarrolla en un bucle de espera hasta que podamos leer la bandera de finalización en el espacio de direcciones del proceso inyectado, en la misma estructura de datos que allí colocamos. Una vez terminado, leeremos igualmente el resultado de vuelta de nuestro código.
El punto 4 libera la memoria inyectada cierra los Handles abiertos al proceso y al thread y retorna el resultado del código inyectado.
Las funciones GetFirstThreadID y GetProcessId no son API. La primera busca el primer Thread de un proceso y sobre ese vamos a trabajar. La segunda realmente no nos hace falta, sólo la uso para buscar el Pid de un proceso sabiendo su nombre (devuelve el primer proceso que encuentra con ese nombre). La uso en el ejemplo.
Un poco lioso pero espero que se entienda bien.
Saludos.