He de aclarar que, al igual que los hooks descritos estás diseñados y compilados para entorno 32bits, el Unhooker que expongo cumple la misma característica, si bien funciona en win7 y win8 de 64bits, sólo lo hará en procesos de 32bits ejecutándose en estos sistemas.
El tema no es sencillo puesto que hay numerosas formas de hacer un Hook a una API. Voy a explicar como deshacernos del Hook más habitual tipo trampolín. Recordad que este Hook cambia los primeros bytes del código una API para sustituirlos por un salto al código que engancha dicha API. Con esto en mente, el antihook tiene un planteamiento, basta con reconstituir los bytes originales de esa API para “sanarla”. Nos enfrentamos a dos problemas, por un lado pretendemos hacerlo desde un proceso externo y, por otro, debemos conocer cuál es el código original de la API en cuestión. Asumiendo que no todos los procesos tienen esa API hookeada, es decir, que nuestro proceso “deshookeador” no está hookeado, podemos conocer el código original usando la API GetProcAddress que nos da un puntero al punto de entrada de la API buscada.
La solución más sencilla sería escribir directamente en el espacio de direcciones del proceso a “sanar”, en concreto la dirección que corresponde a la API hookeada, otra forma más sofisticada sería mediante una inyección de código. Dado que existen sistemas de protección anti inyección de dlls, la elección sería la inyección directa de código. Si el proceso tiene hookeadas las APIS que nos hacen falta inyectar, fallaremos. Podéis consultar tutoriales de inyección directa en 1, 2 y 3.
Disponiendo de ambos sistemas en una misma herramienta, tendremos más posibilidad de éxito para ir deshookeando las APIs que nos interesen, y eso es lo que vamos a hacer.
Manos a la Obra:
Voy a mostrar como hacerlo de las dos formas propuestas.
1.- Escribiendo directamente en el proceso:
Es una forma fácil y eficaz, solo necesitamos tener libres de hook dos APIs pero no nos permite realizar más acciones en el código anfitrión.
delphi
// Escribiendo directamente en el proceso function UnHooker_WriteMemory(Pid: DWORD; APIName, DLLName: PChar; nBytes: integer): BOOL; var hProc: THandle; // El handle del proceso en el que inyectaremos API: Pointer; // Lugar de memoria donde copiaremos nuestra función OldProtect: DWORD; // Tipo de protección de acceso a la memoria del proceso BytesWritten: DWORD; begin API:= GetProcAddress(GetModuleHandleA(DllName), APIName); hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid); VirtualProtectEx(hProc, API, nBytes, PAGE_EXECUTE_READWRITE, OldProtect); WriteProcessMemory(hProc, API, API, nBytes, BytesWritten); VirtualProtectEx(hProc, API, nBytes, OldProtect, PDWORD(nil)^); Result:= (BytesWritten = nBytes); end;
2.- Mediante Inyección directa de código:
La inyección directa es más compleja de usar y diseñar pero nos permite realizar más cosas con el código que inyectemos. Voy a realizar una inyección con CreateRemoteThread, para lo que me hacen falta una función y un parámetro.
delphi
type TParamFL = record APIName: array[0..63] of char; // nombre de la API a sanar DllName: array[0..63] of char; // Nombre de la dll que la contiene CodeBuffer: array[0..4] of BYTE; // Buffer con el código correcto SizeBuffer: integer; // Tamaño del buffer end; PTParamFL = ^TParamFL;
delphi
type ADWORD = array [0..0] of DWORD; PADWORD = ^ADWORD; // Inyectando código directo {No cambiar de sitio Este es el código inyectado} function UnHooker(P: PTParamFL):BOOL; stdcall; var Po: Pointer; _GetProcAddress: PGetProcAddress; _GetModuleHandleA: PGetModuleHandleA; _VirtualProtectEx: PVirtualProtectEx; API: Pointer; OldProtect: DWORD; SGetProcAddress: PCHAR; // Cadenas locales SGetModuleHandleA: PCHAR; SVirtualProtectEx: PCHAR; KB: UINT; // hModule de Kernel32.dll IED: PImageExportDirectory; Names: PDWORD; Name: PCHAR; EntryPoints: PADWORD; // Puntos de entrada a las funciones en la dll Index: PWORD; n,i: Cardinal; begin asm call @sigue db 'GetProcAddress',0 // 15 db 'GetModuleHandleA',0 // 17 db 'VirtualProtectEx',0 // 17 @sigue: pop eax // eax apunta a las cadenas mov SGetProcAddress, eax add eax, 15 // tamaño de _GetProcAddress + 0 mov SGetModuleHandleA, eax add eax, 17 mov SVirtualProtectEx, eax mov eax, fs:[$30] // Puntero al PEB mov eax, [eax + $0C] // Puntero a PPEB_LDR_DATA mov eax, [eax + $0C] // Puntero a InLoadOrderModuleList mov eax, [eax] // InLoadOrderModuleList de ntdll.dll mov eax, [eax] // InLoadOrderModuleList de kernel32.dll mov eax, [eax + $18] // BaseAddress de kernel32.dll mov KB, eax // Encuentro PIMAGE_EXPORT_DIRECTORY mov eax, [eax + $3C] add eax, $78 add eax, KB mov eax, [eax] add eax, KB mov IED, eax end; Result:= false; Names:= PDWORD(Cardinal(IED.AddressOfNames) + KB); EntryPoints:= Pointer(Cardinal(IED.AddressOfFunctions) + KB); Index:= PWORD(DWORD(IED.AddressOfNameOrdinals) + KB); @_GetProcAddress:= nil; @_GetModuleHandleA:= nil; n:= 0; i:= 0; while (n < IED.NumberOfNames) and (i <> 14) do begin if(Index^ >= IED.NumberOfFunctions) then continue; Name:= PCHAR(Names^ + KB); @_GetProcAddress:= Pointer(EntryPoints[Index^] + KB); i:= 0; while (SGetProcAddress[i]<>#0) and (Name[i] = SGetProcAddress[i]) do inc(i); inc(n); inc(Names); inc(Index); end; // Encontrada GetProcAddress cargamos las APIs restantes if @_GetProcAddress <> nil then begin @_GetModuleHandleA:= _GetProcAddress(KB, SGetModuleHandleA); if @_GetModuleHandleA <> nil then begin API:= _GetProcAddress(_GetModuleHandleA(P.DllName), P.APIName); if API <> nil then begin @_VirtualProtectEx:= _GetProcAddress(KB, SVirtualProtectEx); if @_VirtualProtectEx <> nil then begin //Copiamos el Buffer en la API UNHOOK // Asignamos permiso de escritura _VirtualProtectEx(THANDLE(-1), API, P.SizeBuffer, PAGE_EXECUTE_READWRITE, OldProtect); for n:=0 to P.SizeBuffer-1 do PBYTE(DWORD(API)+n)^:= P.CodeBuffer[n]; // Restauramos permisos _VirtualProtectEx(THandle(-1), API, P.SizeBuffer, OldProtect, PDWORD(nil)^); Result:= true; end; end; end; end; end; {No cambiar de sitio Código inyector} function UnHooker_Inyector(Pid: DWORD; APIName, DLLName: PChar): BOOL; var ExitCode: DWORD; hThread: THandle; hProc: THandle; // El handle del proceso en el que inyectaremos Param: TParamFL; // El tipo de dato de la estructura TamFun: UINT; // El tamaño de la función a inyectar BFunc: Pointer; // Lugar de memoria donde copiaremos nuestra función BParam: Pointer; // Lugar de memoria donde copiaremos los parametros BytesWritten: DWORD; begin hThread:= 0; //Cargamos datos en la estructura lstrcpy(Param.DllName, DllName); lstrcpy(Param.APIName, APIName); Param.SizeBuffer:= Sizeof(Param.CodeBuffer); CopyMemory(@Param.CodeBuffer[0], GetProcAddress(GetModuleHandle(DllName), APIName), Param.SizeBuffer); //Abrimos el proceso en el que nos inyectaremos hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid); //Reservamos espacio para nuestra estructura en el proceso a inyectar y la escribimos BParam:= VirtualAllocEx(hProc, nil, sizeof(TParamFL), MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProc, BParam, @Param, sizeof(TParamFL), BytesWritten); //Calculamos el tamaño de la función a inyectar TamFun:= UINT(@UnHooker_Inyector) - UINT(@UnHooker); //Reservamos espacio para la función, escribimos en él y creamos un hilo BFunc:= VirtualAllocEx(hProc, nil, TamFun, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProc, BFunc, @UnHooker, TamFun, BytesWritten); hThread:= CreateRemoteThread(hProc, nil, 0, BFunc, BParam, 0, PDWORD(nil)^); if hThread <>0 then begin // Labores de limpieza... WaitForSingleObject(hThread, INFINITE ); // Espero a que se ejecute el Thread GetExitCodeThread(hThread, ExitCode); // Recojo el resultado CloseHandle(hThread); // Cierro el Handle hThread VirtualFreeEx(hProc, BParam, 0, MEM_RELEASE); // Libero memoria VirtualFreeEx(hProc, BFunc, 0, MEM_RELEASE); // Libero memoria end; CloseHandle(hProc); Result:= BOOL(ExitCode); end;
Como veis, el código inyectado busca las APIs que va a utilizar para poder realizar su tarea y recibe como parámetro una estructura con el nombre de la API, de la dll que la contiene y un pequeño buffer con el código sano (en principio con unos 5 o 10 bytes deberían ser suficientes). En un código inyectado de forma directa no se pueden usar APIs sin más, funciona como un shellcode y debe, bien recibir las direcciones de esas APIs, o encontrarlas por sí mismo. Nosotros las vamos a encontrar con una técnica que ya expliqué aquí en asm/C y que ahora traslado a asm/delphi. El resto del trabajo es copiar bytes. A alguno le extrañará que la copia de bytes sea en un bucle uno a uno, es para ahorrarme buscar más APIs.
Si compiláis el código, no cambiéis de sitio las funciones UnHooker y UnHooker_Inyector pues sus punteros son usados para calcular el tamaño de la inyección
Para terminar y como respuesta a la pregunta que se hizo aquí en la que un Hook a la indocumentada API LdrLoadDll impedía inyectar una dll, las técnicas que muestro pueden dar con la solución al problema siempre que no tengamos hookeadas APIs como VirtualAllocEx.
Esta pequeña herramienta puede no ser la solución de todos los Hooks a la API tipo trampolín pero va a ser eficaz en un alto porcentaje de casos y es un buen ejercicio de programación.
Subo el código fuente y compilado, así como una aplicación que realiza un Hook a MessageBox para demostrar que el sistema funciona eliminando dicho Hook.
Espero que os haya resultado interesante.
Saludos.