Ir al contenido



Foto

API UnHooker deshaciendo un Hook a la API tipo trampolín


  • Por favor identifícate para responder
2 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.844 mensajes
  • LocationMadrid - España

Escrito 07 mayo 2014 - 11:50

Tras la publicación del tutorial HOOK a la API en delphi y en C (trampolín) en la que explicaba cómo hacer un Hook a una API de forma automática, a deshacerlo, a suspenderlo y volver a ponerlo en marcha, quedaba pendiente deshacer  hook que no hemos hecho nosotros.

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
  1. // Escribiendo directamente en el proceso
  2. function UnHooker_WriteMemory(Pid: DWORD; APIName, DLLName: PChar; nBytes: integer): BOOL;
  3. var
  4.   hProc:      THandle;  // El handle del proceso en el que inyectaremos
  5.   API:        Pointer;  // Lugar de memoria donde copiaremos nuestra función
  6.   OldProtect: DWORD;    // Tipo de protección de acceso a la memoria del proceso
  7.   BytesWritten: DWORD;
  8. begin
  9.   API:= GetProcAddress(GetModuleHandleA(DllName), APIName);
  10.   hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  11.   VirtualProtectEx(hProc, API, nBytes, PAGE_EXECUTE_READWRITE, OldProtect);
  12.   WriteProcessMemory(hProc, API, API, nBytes, BytesWritten);
  13.   VirtualProtectEx(hProc, API, nBytes, OldProtect, PDWORD(nil)^);
  14.   Result:= (BytesWritten = nBytes);
  15. 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
  1. type
  2.   TParamFL = record
  3.     APIName: array[0..63] of char;  // nombre de la API a sanar
  4.     DllName: array[0..63] of char;  // Nombre de la dll que la contiene
  5.     CodeBuffer: array[0..4] of BYTE; // Buffer con el código correcto
  6.     SizeBuffer: integer;            // Tamaño del buffer
  7.   end;
  8.   PTParamFL = ^TParamFL;


delphi
  1.   type
  2.   ADWORD = array [0..0] of DWORD;
  3.   PADWORD = ^ADWORD;
  4.  
  5. // Inyectando código directo
  6. {No cambiar de sitio
  7. Este es el código inyectado}
  8. function UnHooker(P: PTParamFL):BOOL; stdcall;
  9. var
  10.   Po: Pointer;
  11.   _GetProcAddress: PGetProcAddress;
  12.   _GetModuleHandleA: PGetModuleHandleA;
  13.   _VirtualProtectEx: PVirtualProtectEx;
  14.   API: Pointer;
  15.   OldProtect: DWORD;
  16.  
  17.   SGetProcAddress:  PCHAR;      // Cadenas locales
  18.   SGetModuleHandleA: PCHAR;
  19.   SVirtualProtectEx: PCHAR;
  20.  
  21.   KB:    UINT;                  // hModule de Kernel32.dll
  22.   IED:  PImageExportDirectory;
  23.   Names: PDWORD;
  24.   Name:  PCHAR;
  25.   EntryPoints: PADWORD;          // Puntos de entrada a las funciones en la dll
  26.   Index: PWORD;
  27.   n,i: Cardinal;
  28. begin
  29.   asm
  30.   call @sigue
  31.   db 'GetProcAddress',0    // 15
  32.   db 'GetModuleHandleA',0  // 17
  33.   db 'VirtualProtectEx',0  // 17
  34.   @sigue:
  35.   pop eax                  // eax apunta a las cadenas
  36.   mov SGetProcAddress, eax
  37.   add eax, 15              // tamaño de _GetProcAddress + 0
  38.   mov SGetModuleHandleA, eax
  39.   add eax, 17
  40.   mov SVirtualProtectEx, eax
  41.  
  42.   mov eax, fs:[$30]    // Puntero al PEB
  43.   mov eax, [eax + $0C] // Puntero a PPEB_LDR_DATA
  44.   mov eax, [eax + $0C] // Puntero a InLoadOrderModuleList
  45.   mov eax, [eax]      // InLoadOrderModuleList de ntdll.dll
  46.   mov eax, [eax]      // InLoadOrderModuleList de kernel32.dll
  47.   mov eax, [eax + $18] // BaseAddress de kernel32.dll
  48.   mov KB, eax
  49.  
  50.   // Encuentro PIMAGE_EXPORT_DIRECTORY
  51.   mov eax, [eax + $3C]
  52.   add eax, $78
  53.   add eax, KB
  54.   mov eax, [eax]
  55.   add eax, KB
  56.   mov IED, eax
  57.   end;
  58.  
  59.   Result:= false;
  60.   Names:= PDWORD(Cardinal(IED.AddressOfNames) + KB);
  61.   EntryPoints:= Pointer(Cardinal(IED.AddressOfFunctions) + KB);
  62.   Index:= PWORD(DWORD(IED.AddressOfNameOrdinals) + KB);
  63.  
  64.   @_GetProcAddress:= nil;
  65.   @_GetModuleHandleA:= nil;
  66.   n:= 0; i:= 0;
  67.   while (n < IED.NumberOfNames) and (i <> 14) do
  68.   begin
  69.     if(Index^ >= IED.NumberOfFunctions) then continue;
  70.     Name:= PCHAR(Names^ + KB);
  71.     @_GetProcAddress:= Pointer(EntryPoints[Index^] + KB);
  72.     i:= 0;
  73.     while (SGetProcAddress[i]<>#0) and (Name[i] = SGetProcAddress[i]) do  inc(i);
  74.     inc(n);
  75.     inc(Names);
  76.     inc(Index);
  77.   end;
  78.  
  79.   // Encontrada GetProcAddress cargamos las APIs restantes
  80.   if @_GetProcAddress <> nil then
  81.   begin
  82.     @_GetModuleHandleA:= _GetProcAddress(KB, SGetModuleHandleA);
  83.     if @_GetModuleHandleA <> nil then
  84.     begin
  85.       API:= _GetProcAddress(_GetModuleHandleA(P.DllName), P.APIName);
  86.       if API <> nil then
  87.       begin
  88.         @_VirtualProtectEx:= _GetProcAddress(KB, SVirtualProtectEx);
  89.         if @_VirtualProtectEx <> nil then
  90.         begin
  91.           //Copiamos el Buffer en la API  UNHOOK
  92.           // Asignamos permiso de escritura
  93.           _VirtualProtectEx(THANDLE(-1), API, P.SizeBuffer, PAGE_EXECUTE_READWRITE, OldProtect);
  94.           for n:=0 to P.SizeBuffer-1 do
  95.             PBYTE(DWORD(API)+n)^:= P.CodeBuffer[n];
  96.           // Restauramos permisos
  97.           _VirtualProtectEx(THandle(-1), API, P.SizeBuffer, OldProtect, PDWORD(nil)^);
  98.           Result:= true;
  99.         end;
  100.       end;
  101.     end;
  102.   end;
  103. end;
  104.  
  105. {No cambiar de sitio
  106. Código inyector}
  107. function UnHooker_Inyector(Pid: DWORD; APIName, DLLName: PChar): BOOL;
  108. var
  109.   ExitCode: DWORD;
  110.   hThread:  THandle;
  111.   hProc:    THandle;  // El handle del proceso en el que inyectaremos
  112.   Param:    TParamFL;  // El tipo de dato de la estructura
  113.   TamFun:  UINT;      // El tamaño de la función a inyectar
  114.   BFunc:    Pointer;  // Lugar de memoria donde copiaremos nuestra función
  115.   BParam:  Pointer;  // Lugar de memoria donde copiaremos los parametros
  116.   BytesWritten: DWORD;
  117.  
  118. begin
  119.   hThread:= 0;
  120.   //Cargamos datos en la estructura
  121.   lstrcpy(Param.DllName, DllName);
  122.   lstrcpy(Param.APIName, APIName);
  123.   Param.SizeBuffer:= Sizeof(Param.CodeBuffer);
  124.   CopyMemory(@Param.CodeBuffer[0], GetProcAddress(GetModuleHandle(DllName), APIName), Param.SizeBuffer);
  125.  
  126.   //Abrimos el proceso en el que nos inyectaremos
  127.   hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  128.  
  129.   //Reservamos espacio para nuestra estructura en el proceso a inyectar y la escribimos
  130.   BParam:= VirtualAllocEx(hProc, nil, sizeof(TParamFL), MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
  131.   WriteProcessMemory(hProc, BParam, @Param, sizeof(TParamFL), BytesWritten);
  132.  
  133.   //Calculamos el tamaño de la función a inyectar
  134.   TamFun:= UINT(@UnHooker_Inyector) - UINT(@UnHooker);
  135.  
  136.   //Reservamos espacio para la función, escribimos en él y creamos un hilo
  137.   BFunc:= VirtualAllocEx(hProc, nil, TamFun, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  138.   WriteProcessMemory(hProc, BFunc, @UnHooker, TamFun, BytesWritten);
  139.   hThread:= CreateRemoteThread(hProc, nil, 0, BFunc, BParam, 0, PDWORD(nil)^);
  140.   if hThread <>0 then
  141.   begin
  142.     // Labores de limpieza...
  143.     WaitForSingleObject(hThread, INFINITE ); // Espero a que se ejecute el Thread
  144.     GetExitCodeThread(hThread, ExitCode);    // Recojo el resultado
  145.     CloseHandle(hThread);                    // Cierro el Handle hThread
  146.     VirtualFreeEx(hProc, BParam, 0, MEM_RELEASE); // Libero memoria
  147.     VirtualFreeEx(hProc, BFunc,  0, MEM_RELEASE); // Libero memoria
  148.   end;
  149.  
  150.   CloseHandle(hProc);
  151.  
  152.   Result:= BOOL(ExitCode);
  153. 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.

Archivos adjuntos


  • 0

#2 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 543 mensajes
  • LocationEspaña

Escrito 07 mayo 2014 - 11:44

Muchas gracias por el aporte que, como siempre, es magnifico...

(y) (y)

  • 0

#3 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 13.962 mensajes
  • LocationMéxico

Escrito 08 mayo 2014 - 10:15

Que digo que no haya dicho antes ya, vamos a tener que crear al equipo Super-API :)

Saludos
  • 0