Jump to content


Photo

[LAZARUS] HOOK a la API64


  • Please log in to reply
5 replies to this topic

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 25 December 2016 - 07:17 AM

Hace tiempo que deseaba migrar mi sistema de 32 bits para realizar hooks a la API  a 64bit. Aprovechando que disponía de un poco de tiempo, lo he hecho.

El sistema lo he desarrollado en Lazarus 32 compilando a 64bits en modo compatibilidad con delphi (-Mdelhpi). Y he encontrado dificultades con el debugger en modo ensamblador de Lazarus, pues no está muy conseguido y  "se pierde", defecto que he solventado con imaginación.

Un problema añadido en el sistema API64 es que difiere de la versión 32bits en muchas cosas. La API32 suele comenzar así:


delphi
  1. MOV EDI , EDI
  2. PUSH EBP
  3. MOV EBP , ESP

Lo que supone que en casi todas se pueden sobrescribir los primeros 5 bytes sin problemas y en caso de no comenzar así y presentar una instrucción de llamada relativa (call o Jmp), se recalculaban los offset con ayuda de un desensamblador de longitud. Sin embargo una gran cantidad de API64 no comienza de este modo, algunas presentan llamadas inmediatas, comparaciones, etc.

En la versión API64 aparece un serio  problema y es que el espacio de direcciones sobrepasa con creces el espacio disponible para saltos relativos. Sustituirlos con saltos absolutos es verdaderamente endiablado, con lo que opté por localizar espacio de memoria cercano a la API lo que facilitaba las cosas, pero hacía falta un desensamblador de longitud para 64bits que permitiera, a su vez, localizar cuando una instrucción asm se refería a direcciones relativas. En su día usé uno escrito en C para 32bits, en esta ocasión traduje y adapté a Lazarus uno de 64 bits: LDasm64. Podéis encontrar una referencia técnica al juego de instrucciones asm 64bits aquí .

De esta forma diseñé el bloque de memoria trampolín, cercano a la API64 con la siguiente estructura en pseudocódigo:


delphi
  1. ABS_JMP a HookAPI (FAR)
  2. ToAPI   bytes copiados de la API original (función de vuelta a ella)
  3. REL_JMP Salto relativo a la API original

Al estar, este bloque, cercano a la API, el Hook se puede hacer con los 5 bytes de un salto relativo, mínimo espacio necesario. Ese salto se hace al offset 0 del anterior bloque que presenta hardcodeado un salto absoluto a la función hook, ya en la zona de memoria de nuestra aplicación. Luego, nuestra app podrá realizar la llamada a la API original a través del trampolín, realizando un call lejano a ToAPI que ejecutará las primeras instrucciones del a API original (que tenía copiadas y recalculadas las instrucciones asm en el caso de presentar offsets relativos) para luego realizar un salto relativo a la API original, justo después de los 5 bytes sobrescritos.

El siguiente código muestra como localizar el bloque de memoria cercano a la API y como se prepara para saltar y retornar del hook:


delphi
  1. procedure InstallHookFromAddr(HookAPI: Pointer; var ToAPI: Pointer; API: Pointer; Resume: BOOL);
  2. var
  3.   NBytes: DWORD;
  4.   OldProtect: DWORD;
  5.   si: SYSTEM_INFO;
  6.   Block: ULONG_PTR;
  7.   MaxBlock: ULONG_PTR;
  8.   RelayMemoy: Pointer;
  9.   RelaySize: DWORD;
  10. begin
  11.   NBytes:= GetOverloadBytes(API); // Bytes a sobreescribir de la API
  12.   Block:=  (ULONG_PTR(API) shr 32)shl 32;
  13.   MaxBlock:= ((ULONG_PTR(API) shr 32) + 1)shl 32;
  14.   RelayMemoy:= nil;
  15.   RelaySize:= NBytes + sizeof(ABS_JMP) + SIZE_OF_HOOK;
  16.   ToAPI:= nil;
  17.  
  18.   // Buscamos un bloque de memoria Near al espacio de la API de Windows
  19.   GetSystemInfo(si);
  20.   while (Block < MaxBlock) and  (RelayMemoy = nil) do
  21.   begin
  22.     RelayMemoy := VirtualAlloc(Pointer(Block+1), RelaySize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  23.     inc(Block, si.dwAllocationGranularity);
  24.   end;
  25.   if RelayMemoy <> nil then
  26.   begin
  27.     // Preparar la función ToAPI
  28.     // Autorizo escribir en ToAPI
  29.     VirtualProtectEx(THandle(-1), RelayMemoy, RelaySize, PAGE_EXECUTE_READWRITE, @OldProtect);
  30.     Write_ABS_JMP(RelayMemoy, HookAPI);   // Salto a HookAPI
  31.     ToAPI:= Pointer(ULONG_PTR(RelayMemoy) + sizeof(ABS_JMP));
  32.     ASMmemcpy(ToAPI, API, NBytes); // Copio los primeros bytes de la api
  33.     Write_REL_JMP(Pointer(ULONG_PTR(ToAPI) + NBytes), Pointer(ULONG_PTR(API) + NBytes));
  34.     VirtualProtectEx(THandle(-1), RelayMemoy, RelaySize, OldProtect, nil);
  35.     // Preparar la API a Hookear
  36.     if Resume then ResumeHookFromAddr(RelayMemoy, API);
  37.   end;
  38.   FlushInstructionCache(GetCurrentProcess, nil, 0);
  39. end;

El siguiente código muestra como sobrescribir la API original:


delphi
  1. procedure ResumeHookFromAddr(HookFunc, API: Pointer);
  2. var
  3.   OldProtect: DWORD;
  4. begin
  5.   if (HookFunc = nil) or (API = nil) then exit;
  6.  
  7.   // Autorizo escribir en la API a Hookear
  8.   VirtualProtectEx(THandle(-1), API, sizeof(ABS_JMP), PAGE_EXECUTE_READWRITE, @OldProtect);
  9.   Write_REL_JMP(API, HookFunc);  // Escribo el salto
  10.   VirtualProtectEx(THandle(-1), API, sizeof(ABS_JMP), OldProtect, nil);
  11. end;

En definitiva, es sistema es el clásico hook a la API de 5 bytes descrito aquí con las modificaciones necesaria para trabajar en 64 bits. Se trata de la primera versión del sistema que he probado con éxito con APIs conflictivas en 64 bits como MessageBox y IsProcessorFeaturePresent, pero que posiblemente necesite alguna revisión.
 
Recomiendo la lectura del tutorial HOOK a la API en delphi y en C (trampolín) para aclarar conceptos básicos del Hook a la API con esta técnica.

Lo he escrito para Lazarus 32bits compilando a 64bits porque no dispongo de una versión delphi que me permita compilar para 64 bits y porque tengo Lazarus 32 bits instalado. Me da mucha pereza desinstalarlo para instalar la versión 64, que por otro lado creo que no permite compilar código de 32 bits, aunque esto, puede que halla variado.

Y os preguntaréis porqué realizar un hook a la API de 64 bits y no conformarme con la de 32bits. La respuesta es doble, por un lado abarcas los procesos de 64 pudiendo compilar dll e inyectarlas en esos procesos. La segunda razón es por prurito y curiosidad personal, no me puedo estar quieto.

Esperando que todo esto sea de vuestro interés, subo todo el código en un proyecto Lazarus de ejemplo.
 
 
Saludos.

Attached Files


  • 3

#2 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14460 posts
  • LocationMéxico

Posted 25 December 2016 - 08:10 AM

Usted no para amigo. Es usted muy inquieto. :D

FELIZ NAVIDAD!!!
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 25 December 2016 - 08:28 AM

Feliz Navidad. :)

 

 

Saludos.


  • 0

#4 BDWONG

BDWONG

    Member

  • Miembros
  • PipPip
  • 28 posts

Posted 25 December 2016 - 10:45 PM

Excelente escafandra muchas gracias por estos aportes.

Saludos.... (y)


  • 0

#5 Dante

Dante

    Advanced Member

  • Miembros
  • PipPipPip
  • 89 posts

Posted 26 December 2016 - 07:26 AM

Que buen aporte para la Navidad.

 

FELIZIDADES A TODOS POR LA NAVIDA.


  • 0

#6 sir.dev.a.lot

sir.dev.a.lot

    Advanced Member

  • Miembros
  • PipPipPip
  • 545 posts
  • Location127.0.0.1

Posted 26 December 2016 - 11:02 PM

Gracias por el Aporte!

 

Saludos!


  • 0




IP.Board spam blocked by CleanTalk.