ClsHookAPI
¿Para qué sirve?
La clase ClsHookAPI nos permite crear aplicaciones en las que necesitemos hacer uso de un API Hooking de una forma muy sencilla, de forma que nos permita centrarnos en la programación que reciba el hook.
¿Cómo funciona?
La clase lo que hace es inyectar el propio Rootkit en el proceso que tratamos de Hookear mediante ésta técnica. Una vez que tenemos todo el ejecutable en el mismo proceso, el Rootkit instala los Hooks redirigiendo las API's hacia las funciones del Rootkit inyectado en ese mismo proceso.
¿Qué consigues con eso?
Al estar el Rootkit perfectamente inyectado en el proceso que queremos hookear, al redirigir las API's hacia las funciones que reciben el hook, las variables utiliadas en esa función, así como las llamadas a otras fuciones, API's, etc funcionarán perfectamente, lo que nos evita tener que preocuparnos de cargar las API's dinámicamente, deltas, variables imposibles, etc.
¿Cuanto facilita la clase el proceso?
Al MÁXIMO!. La verdad no se me ocurrió ningúna forma más sencilla.
¿Cómo funciona?
Lo primero es llamar al constructor de la clase y setear el proceso que queremos hookear:
Código
ClsHookAPI Hook; Hook.SetHookedProcessName("ejecutable.exe");
La función SetHookedProcessName() devuelve TRUE si se puedo abrir el proceso, y FALSE si ocurrió algún error, como que el proceso no esté en ejecución.
Luego debemos definir la función que recibirá el hook. La función debe de usar la convención de llamada __stdcall para que no corrompa la pila. En la función podemos usar cualquier API o variable, incluso podemos llamar a la misma API que estamos Hookeando sin temor a que caiga en el hook. En éste caso hookearé MessageBoxA:
Código
int __stdcall HookedMessageBoxA(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType) { //Llamamos a MessageBoxA normalmente, cambiando el mensaje que muestra return MessageBoxA(0,"MessageBoxA Hookeada!!!",lpCaption,0); }
Ahora ya podemos instalar el Hook, para eso utilizamos el operador '<<' para setear el nombre de la DLL y la API, y el operador '>>' para indicar a donde queremos redirigir la API:
Código
Hook<<"USER32.MessageBoxA"; Hook>>(unsigned long)HookedMessageBoxA;
Ya tendríamos la API MessageBoxA Hookeada .
Nota: #Es necesario setear la dirección a la que se redirigirá la API justo despues de indicar la API que queremos Hookear.
¿Puedo Hookear varias API's a la vez?
Por supuesto, aquí un ejemplo de 3 Hooks simultáneos:
Código
#pragma comment (linker,"/ENTRY:main") #include "ClsHookApi.h" int __stdcall HookedMessageBoxA(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType); void __stdcall HookedExitProcess(UINT uExitCode); BOOL __stdcall HookedBeep(DWORD dwFreq,DWORD dwDuration); int main() { ClsHookAPI miHook; if(miHook.SetHookedProcessName("mensaje.exe")) { miHook<<"USER32.MessageBoxA"; miHook>>(unsigned long)HookedMessageBoxA; miHook<<"KERNEL32.ExitProcess"; miHook>>(unsigned long)HookedExitProcess; miHook<<"KERNEL32.DLL.Beep"; miHook>>(unsigned long)HookedBeep; } return 0; } int __stdcall HookedMessageBoxA(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType) { return MessageBoxA(0,"MessageBoxA Hookeada!!!",lpCaption,0); } void __stdcall HookedExitProcess(UINT uExitCode) { MessageBoxA(0,"Has llamado a ExitProcess!","xD",0); return ExitProcess(uExitCode); } BOOL __stdcall HookedBeep(DWORD dwFreq,DWORD dwDuration) { Beep(1000,100); Beep(1000,100); return Beep(1000,100); }
¿Vale, me la das ya?
Claro . Pongo aquí el código aunque es un poco largo. Más abajo ponto el proyecto de ejemplo en descarga.
ClsHookAPI.h
Código
//---------------------------------------------------------------------------------------------------- //Autor: Hacker_Zero //Fecha: 11 enero 2010 //Nombre: ClsHookApi //Descripción: // Clase en C++ que permite hookear API's de forma simultánea, facilitando enormemente //la programación de las funciones que reciven el hook así como la instalación de los //propios hooks. //---------------------------------------------------------------------------------------------------- //Código liberado bajo la GNU Public License (GPL) <http://www.gnu.org/licenses/gpl-3.0.html> //Eres libre de utilizar, modificar y distribuír ésta clase siempre que mantengas ésta cabecera //---------------------------------------------------------------------------------------------------- #include <windows.h> #include <Tlhelp32.h> #include <vector> #include <string.h> using namespace std; struct HookStruct { LPSTR lpAPI; LPSTR lpHookFunction; CHAR lpBuffer[11]; HookStruct* previousHookStruct; HookStruct* nextHookStruct; }; #ifndef HOOKAPI_H #define HOOKAPI_H void HookApi(HookStruct* miHookStruct); void mCopyMemory(LPSTR bEscritura,LPSTR bLectura,DWORD lenBuffer); void ControlFunction(); void Caller(); void UninstallHooks(HookStruct* miHookStruct); void RestoreHooks(HookStruct* miHookStruct); class ClsHookAPI { public: ClsHookAPI(); ClsHookAPI& operator<<(LPSTR HookName); ClsHookAPI& operator>>(unsigned long lpFunctionHook); bool SetHookedProcessName(LPSTR ProcessName); private: void AddFunction(HookStruct miHookStruct); void InstallHook(HookStruct* miHookStruct); vector<HookStruct> Hooks; BOOL ExeInjected; HANDLE hProcess; LPSTR InjectedExeBaseAddress; HookStruct* lastInjectedHookStruct; }; #endif
ClsHookAPI.cpp
Código
#include "ClsHookApi.h" //Constructor de la clase ClsHookAPI::ClsHookAPI() { InjectedExeBaseAddress=NULL; lastInjectedHookStruct=NULL; ExeInjected=false; } //Sobrecarga del operador '<<' ClsHookAPI& ClsHookAPI::operator<<(LPSTR HookName) { //Obtenemos la DLL y la API de HookName string strHookName; strHookName.append(HookName); string strDllName=strHookName.substr(0,strHookName.find_last_of(".")); string strApiName=strHookName.substr(strHookName.find_last_of(".")+1); //Inicializamos la estructura y seteamos el puntero a la API HookStruct miHookStruct; miHookStruct.lpAPI=(LPSTR)GetProcAddress(LoadLibraryA(strDllName.c_str()),strApiName.c_str()); //Añadimos la estructura al Array AddFunction(miHookStruct); return *this; } //Sobrecarga del operador '>>' ClsHookAPI& ClsHookAPI::operator>>(unsigned long lpFunctionHook) { Hooks.back().lpHookFunction=(LPSTR)lpFunctionHook; InstallHook(&Hooks.back()); return *this; } void ClsHookAPI::AddFunction(HookStruct miHookStruct) { //Si es el primer elemento que añadimos if(!Hooks.size()) { miHookStruct.previousHookStruct=NULL; } else { miHookStruct.previousHookStruct=this->lastInjectedHookStruct; } miHookStruct.nextHookStruct=NULL; //Insertamos la estructura en el array Hooks.push_back(miHookStruct); return; } //Seteamos el nombre del proceso en el que inyectaremos bool ClsHookAPI::SetHookedProcessName(LPSTR ProcessName) { HANDLE bakhProcess=hProcess; hProcess=NULL; HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); PROCESSENTRY32 pInfo; pInfo.dwSize=sizeof(PROCESSENTRY32); //Obtenemos el PID del proceso Process32First(hSnapshot,&pInfo); for(;Process32Next(hSnapshot,&pInfo);) { if(!lstrcmpA(pInfo.szExeFile,ProcessName)) { if(OpenProcess(PROCESS_ALL_ACCESS,FALSE,pInfo.th32ProcessID)) { ExeInjected=false; hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pInfo.th32ProcessID); } } } if(!hProcess) { hProcess=bakhProcess; return false; } return true; } void ClsHookAPI::InstallHook(HookStruct* miHookStruct) { //Nos inyectamos en el proceso si no lo hemos hecho ya if(ExeInjected==false) { PIMAGE_DOS_HEADER IDH; PIMAGE_NT_HEADERS INTH; PIMAGE_SECTION_HEADER ISH; LPSTR lpFileName=(LPSTR)GlobalAlloc(GPTR,MAX_PATH); GetModuleFileName(NULL,lpFileName,MAX_PATH); HANDLE hFile=CreateFileA(lpFileName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0); DWORD szFile=GetFileSize(hFile,0); DWORD dwBytes; LPSTR lpFileMaped=(LPSTR)GlobalAlloc(GPTR,szFile); ReadFile(hFile,lpFileMaped,szFile,&dwBytes,0); CloseHandle(hFile); //Obtenemos la cabecera DOS y PE en las estructuras IDH=(PIMAGE_DOS_HEADER)&lpFileMaped[0]; INTH=(PIMAGE_NT_HEADERS)&lpFileMaped[IDH->e_lfanew]; //Creamos el buffer del tamaño del SizeOfImage en el que cargaremos el ejecutable LPSTR ExeBuffer=(LPSTR)VirtualAllocEx(this->hProcess,0,INTH->OptionalHeader.SizeOfImage,MEM_RESERVE|MEM_COMMIT,PAGE_EXECUTE_READWRITE); //Copiamos la cabecera DOS y PE al buffer WriteProcessMemory(this->hProcess,&ExeBuffer[0],&lpFileMaped[0],INTH->OptionalHeader.SizeOfHeaders,0); //Copiamos las secciones en su VirtualOffset en el buffer for(DWORD i=0;i<INTH->FileHeader.NumberOfSections;i++) { ISH=(PIMAGE_SECTION_HEADER)&lpFileMaped[IDH->e_lfanew+sizeof(IMAGE_NT_HEADERS)+sizeof(IMAGE_SECTION_HEADER)*i]; WriteProcessMemory(this->hProcess,&ExeBuffer[ISH->VirtualAddress],&lpFileMaped[ISH->PointerToRawData],ISH->SizeOfRawData,0); } //Calculamos el delta entre la dirección del buffer y el ImageBase DWORD Delta=(((DWORD)ExeBuffer)-INTH->OptionalHeader.ImageBase); //------------------------------------------------------------ /* -Reubicamos la dirección base del ejecutable :D- */ //------------------------------------------------------------ //Obteemos el Image Base Relocation //Copiamos el Image Base Relocation de los datos en el proceso a un buffer en el nuestro para //poder trabajar con él más comodamente PIMAGE_BASE_RELOCATION IBR=(PIMAGE_BASE_RELOCATION)GlobalAlloc(GPTR,sizeof(IMAGE_BASE_RELOCATION)); PIMAGE_BASE_RELOCATION PIBR=(PIMAGE_BASE_RELOCATION)(ExeBuffer+INTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); ReadProcessMemory(hProcess,(LPVOID)PIBR,IBR,sizeof(IMAGE_BASE_RELOCATION),0); //Vamos recorriendo todas las etradas del bloque for(DWORD n=0;IBR->VirtualAddress>0;n++) { //Obtenemos el Bloque de reubicación LPSTR RelocationBlock=(LPSTR)(ExeBuffer+IBR->VirtualAddress); //Obtenemos la primera entrada del bloque LPWORD RelocationEntry=(LPWORD)((LPSTR)PIBR+sizeof(IMAGE_BASE_RELOCATION)); //Recorremos todas las entradas del bloque for(DWORD i=0;i<((IBR->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2);i++,RelocationEntry++) { WORD valor; ReadProcessMemory(this->hProcess,RelocationEntry,&valor,2,0); //Obtenemos los 4 bits que definen el tipo de reubicación DWORD type=valor>>12; //Obtenemos los 12 bits que definen la dirección de la reubicación DWORD offset=valor&0xFFF; //Si el tipo de reubicación es relativo a la dirección base, añadimso el delta if(type==IMAGE_REL_BASED_HIGHLOW) { //Añadimos a la dirección que depende del imagebase original //el delta entre el imagebase y nuestra dirección base LPDWORD newAddr=(LPDWORD)(RelocationBlock+offset); DWORD NewValue; ReadProcessMemory(this->hProcess,newAddr,&NewValue,4,0); NewValue+=Delta; WriteProcessMemory(this->hProcess,newAddr,&NewValue,4,0); } } //Vamos al siguiente bloque PIBR=(PIMAGE_BASE_RELOCATION)(((DWORD)PIBR)+IBR->SizeOfBlock); ReadProcessMemory(this->hProcess,(LPVOID)PIBR,IBR,sizeof(IMAGE_BASE_RELOCATION),0); } GlobalFree(IBR); //--------------------------------------------------------------------- /* -Cargamos los valores de la IAT para poder llamar a las API's- */ //--------------------------------------------------------------------- PIMAGE_THUNK_DATA ITD; PIMAGE_THUNK_DATA PITD; PIMAGE_IMPORT_BY_NAME IIBN; //Comprobamos si hay Import Data Descriptor if(INTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size>0) { //Obtenemos el Import Data Descriptor //Copiamos el Import Data Descriptor de los datos en el proceso a un buffer en el nuestro para //poder trabajar con él más comodamente PIMAGE_IMPORT_DESCRIPTOR IID=(PIMAGE_IMPORT_DESCRIPTOR)GlobalAlloc(GPTR,sizeof(IMAGE_IMPORT_DESCRIPTOR)); PIMAGE_IMPORT_DESCRIPTOR PIID=(PIMAGE_IMPORT_DESCRIPTOR)(ExeBuffer+INTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); ReadProcessMemory(this->hProcess,(LPVOID)PIID,IID,sizeof(IMAGE_IMPORT_DESCRIPTOR),0); //Vamos recorriendo todas las Dll's importadas por el ejecutable for(;IID->Name;) { //Obtenemos la longitud del nombre de la dll DWORD szName=0; CHAR miByte=1; for(int i=0;miByte;i++) { szName=i; ReadProcessMemory(this->hProcess,ExeBuffer+IID->Name+i,&miByte,1,0); } //Obtenemos el nombre de la dll LPSTR lpName=(LPSTR)GlobalAlloc(GPTR,szName+1); ReadProcessMemory(this->hProcess,ExeBuffer+IID->Name,lpName,szName+1,0); //Cargamos la dll HMODULE hLib=LoadLibraryA(lpName); //Obtenemos la dirección al primer miembro del array Image Thunk Data's PITD=(PIMAGE_THUNK_DATA)((DWORD)ExeBuffer+IID->FirstThunk); ITD=(PIMAGE_THUNK_DATA)GlobalAlloc(GPTR,sizeof(IMAGE_THUNK_DATA)); ReadProcessMemory(this->hProcess,PITD,ITD,sizeof(IMAGE_THUNK_DATA),0); //Vamos recorriendo las funciones importadas for(DWORD x=0;ITD->u1.Ordinal;x++) { miByte=1; //Obtenemos la longitud del nombre de la API for(int i=0;miByte;i++) { szName=i; LPSTR puntero=ExeBuffer+ITD->u1.Function+2; puntero+=i; ReadProcessMemory(this->hProcess,puntero,&miByte,1,0); } //Cargamos el Image Import By Name para obtener el nombre IIBN=(PIMAGE_IMPORT_BY_NAME)GlobalAlloc(GPTR,sizeof(IMAGE_IMPORT_BY_NAME)+szName); ReadProcessMemory(this->hProcess,ExeBuffer+ITD->u1.Function,IIBN,sizeof(IMAGE_IMPORT_BY_NAME)+szName,0); //Obtenemos la dirección de la función y la guardamos en la IAT DWORD lpAPI=(DWORD)GetProcAddress(hLib,(LPCSTR)&IIBN->Name); WriteProcessMemory(this->hProcess,ExeBuffer+IID->FirstThunk+x*sizeof(IMAGE_THUNK_DATA),&lpAPI,4,0); PITD++; ReadProcessMemory(this->hProcess,PITD,ITD,sizeof(IMAGE_THUNK_DATA),0); } PIID++; ReadProcessMemory(this->hProcess,(LPVOID)PIID,IID,sizeof(IMAGE_IMPORT_DESCRIPTOR),0); GlobalFree(lpName); GlobalFree(ITD); } GlobalFree(IID); } this->InjectedExeBaseAddress=ExeBuffer; this->ExeInjected=true; } //Cambiamos el ImageBase a la dirección de la función que recibe el hook miHookStruct->lpHookFunction=(LPSTR)((unsigned long)this->InjectedExeBaseAddress+(unsigned long)miHookStruct->lpHookFunction-(unsigned long)GetModuleHandle(NULL)); //Reservamos espacio para la estructura en el proceso en el que inyectamos HookStruct* lpHookStruct=(HookStruct*)VirtualAllocEx(this->hProcess,0,sizeof(HookStruct),MEM_RESERVE|MEM_COMMIT,PAGE_EXECUTE_READWRITE); //Esribimos la estructura en el espacio reservado WriteProcessMemory(this->hProcess,lpHookStruct,miHookStruct,sizeof(HookStruct),0); //Seteamos el valor de nextHookStruct de la HookStruct anterior a la que acabamos de inyectar HookStruct* lastHookStruct=(HookStruct*)GlobalAlloc(GPTR,sizeof(HookStruct)); ReadProcessMemory(this->hProcess,lastInjectedHookStruct,lastHookStruct,sizeof(HookStruct),0); lastHookStruct->nextHookStruct=lpHookStruct; WriteProcessMemory(this->hProcess,lastInjectedHookStruct,lastHookStruct,sizeof(HookStruct),0); //Seteamo el valor de lastInjectedHookStruct this->lastInjectedHookStruct=lpHookStruct; //Obteemos la dirección de la función HookApi DWORD lpHookApi=(DWORD)this->InjectedExeBaseAddress+(DWORD)HookApi-(DWORD)GetModuleHandle(NULL); //Llamamos al EntryPoint CreateRemoteThread(this->hProcess,0,0,(LPTHREAD_START_ROUTINE)lpHookApi,(HookStruct*)lpHookStruct,0,0); return; } //Pequeño código en ASM que por el cual sustituimos el comienzo de la API void __declspec(naked) Caller() { __asm { push 0x00400000 //~Ésta dirección se sustituye en tiempo de ejecución por el puntero a HookStruct push 0x00400000 //~Ésta dirección se sustituye en tiempo de ejecución por la dirección de ControlFunction ret } } void UninstallHooks(HookStruct* miHookStruct) { __asm pushad; //Nos desplazamos al primer Hook for(;miHookStruct->previousHookStruct!=NULL;) { miHookStruct=miHookStruct->previousHookStruct; } //Desinstalamos todos los Hooks for(;miHookStruct!=NULL;) { mCopyMemory(miHookStruct->lpAPI,miHookStruct->lpBuffer,11); FlushInstructionCache(GetCurrentProcess(),0,0); miHookStruct=miHookStruct->nextHookStruct; } __asm popad; } void RestoreHooks(HookStruct* miHookStruct) { __asm pushad; //Nos desplazamos al primer Hook for(;miHookStruct->previousHookStruct!=NULL;) { miHookStruct=miHookStruct->previousHookStruct; } //Volvemos a instalar todos los Hooks for(;miHookStruct!=NULL;) { //Copiamos los X primeros bytes de la api a nuestro buffer mCopyMemory(miHookStruct->lpBuffer,miHookStruct->lpAPI,11); //Cambiamos los la primera dirección que pushea 'Caller' por la un puntero a miHookStruct mCopyMemory((LPSTR)Caller+1,(LPSTR)&miHookStruct,4); //Cambiamos los la segunda dirección que pushea 'Caller' por la dirección de ControlFunction LPSTR dirControlFunction=(LPSTR)ControlFunction; mCopyMemory((LPSTR)Caller+6,(LPSTR)&dirControlFunction,4); //Cambiamos los 10 primeros bytes de la API por la función Caller mCopyMemory(miHookStruct->lpAPI,(LPSTR)Caller,11); FlushInstructionCache(GetCurrentProcess(),0,0); miHookStruct=miHookStruct->nextHookStruct; } __asm popad; } //Función a la que salta la API hookeda en que se inyectó 'Caller' void __declspec(naked) ControlFunction() { __asm { jmp go miHookStruct: nop nop nop nop retornar: _emit 0x68 //push dirRetorno: nop nop nop nop _emit 0xC3 //ret RetornoApi: nop nop nop nop Temp: nop nop nop nop go: //Sólo modificamos eax para no modificar más registros de los que modifica la API //Copiamos a Temp el primer parámetro que hay sobre la pila (lpHookStruct) mov eax,Temp push dword ptr ds:[esp] pop dword ptr ds:[eax] //Copiamos lpHookStruct a miHookStruct push 4 push Temp push miHookStruct call mCopyMemory add esp,12 //Restauramos la pila //Quitamos lpHookStruct de la pila add esp,4 //Copiamos a Temp el primer parámetro que hay sobre la pila (dirección de retorno) mov eax,Temp push dword ptr ds:[esp] pop dword ptr ds:[eax] //Copiamos la dirección de retorno a dirRetorno push 4 push Temp push dirRetorno call mCopyMemory add esp,12 //Restauramos la pila //Quitamos la direcc de retorno de la pila add esp,4 //LLamamos a UninstallHooks para desinstalar todos los hooks antes de llamar a la función que recive el Hook mov eax,miHookStruct push dword ptr ds:[eax] call UninstallHooks add esp,4 //Llamamos a la función que recive el hook y copiamos el retorno a RetornoApi mov eax,miHookStruct mov eax,dword ptr ds:[eax] call dword ptr ds:[eax+0x04] push eax mov eax,RetornoApi pop dword ptr ds:[eax] //Restauramos todos los Hooks mov eax,miHookStruct push dword ptr ds:[eax] call RestoreHooks add esp,4 //Movemos a eax el retorno de la API mov eax,RetornoApi push dword ptr ds:[eax] pop eax //Saltamos a la dirección de retorno jmp retornar } } void HookApi(HookStruct* miHookStruct) { __asm pushad; //Damos permisos de escritura, lectura y ejecución a nuestro buffer DWORD OldProtection; VirtualProtect((LPVOID)miHookStruct->lpBuffer,11,PAGE_EXECUTE_READWRITE,&OldProtection); // Le damos permisos de ejecución a los X primeros bytes de la api VirtualProtect((LPVOID)miHookStruct->lpAPI,11,PAGE_EXECUTE_READWRITE,&OldProtection); //Copiamos los X primeros bytes de la api a nuestro buffer mCopyMemory(miHookStruct->lpBuffer,miHookStruct->lpAPI,11); //Cambiamos los la primera dirección que pushea 'Caller' por la un puntero a miHookStruct mCopyMemory((LPSTR)Caller+1,(LPSTR)&miHookStruct,4); //Cambiamos los la segunda dirección que pushea 'Caller' por la dirección de ControlFunction LPSTR dirControlFunction=(LPSTR)ControlFunction; mCopyMemory((LPSTR)Caller+6,(LPSTR)&dirControlFunction,4); //Cambiamos los 10 primeros bytes de la API por la función Caller mCopyMemory(miHookStruct->lpAPI,(LPSTR)Caller,11); FlushInstructionCache(GetCurrentProcess(),0,0); __asm popad; } //Función que simula RtlCopyMemory void __declspec(naked) mCopyMemory(LPSTR bEscritura,LPSTR bLectura,DWORD lenBuffer) { __asm { pushad mov esi,[esp+0x28] //bLectura mov ecx,[esp+0x2C] //lenBuffer mov edi,[esp+0x24] //bEscritura dec ecx bCopyMemory: dec ecx movsb cmp ecx,0 jge bCopyMemory popad ret } }
Descargar Ejemplo
Nota: El ejecutable hookeado en el proceso de ejemplo se puede encontrar en la carpeta release, junto con el source del mismo.
Saludos