Hola...

Otro paper interesante...

------[ Índice

0.- Conceptos previos

1.- Introducción

2.- Diseño
2.1 - Método Detour convencional
2.1.1 - Flujo
2.1.1 - Stack
2.2 - E8 Method
2.2.1 - Flujo
2.2.2 - Stack
2.3 - Diferencias
2.4 - Safe Hook Handler
2.4.1 - Siendo invisible
2.4.1.1 - Evidencias basadas en el microprocesador
2.4.1.2 - Evidencias no basadas en el microprocesador
2.4.2 - Problemas de seguridad
2.4.2.1 - Llamada a una API hookeada
2.4.2.2 - Mala programación hook handler
2.5 - Conclusion

3.- Implementación
3.1 - Microsoft Detours Library
3.1.1 - Capa en ensamblador
3.1.1.1 - Creación handle_ooh
3.1.1.2 - SafeCall
3.1.1.3 - SafeRet
3.1.1.4 - Otras funciones de bajo nivel
3.1.2 - one_hook_handler
3.1.3 - Llamada a una API hookeada
3.1.4 - Hack de la biblioteca
3.2 - Easy-hook
3.2.1 - Capa en ensamblador
3.2.1.1 - Creación handle_ooh
3.2.2 - Llamada a una API hookeada - TDB
3.2.3 - one_hook_handler

4.- Compilado, binarios & POC
4.1 - poc
4.1.1 - Compilado
4.1.2 - Uso
4.2 - Detours Express 2.1- hacked
4.2.1 - Compilado
4.2.2 - Uso
4.3 - EasyHook 2.5 - unmaged stuff
4.3.1 - Compilado
4.3.2 - Uso

5.- TODO

6.- Testing

7.- Ventajas y posibilidades

8.- Conclusión

9.- Agradecimientos

10.- Referencias

11.- Código fuente y binarios


------[ 0.- Conceptos previos

Nomenclaturas:

.- [T.Index]: trabajos relacionados (apartado 10).
.- [R.Index]: referencias (apartado 11).

Index es el identificador de la nomenclatura.

Para entender el documento puede ser necesario tener conocimientos de:

- Arquitectura x86:

- Arquitectura básica. [R.10]

- Instrucciones básicas. [R.11]

La esencia del documento y el "E8 Method" se pueden entender sin conocer en
profundidad ningún sistema operativo en concreto. Las demostraciones de
concepto (POCs) y los hacks a bibliotecas actuales estarán orientados a
Windows, por ello quizá sea necesario para entender el documento tener
conocimiento de:

- Win32 API [R.3]

- Tipos de hooks y hooks aplicables en Windows [R.4] [R.5] [R.9] [...]

- Tipo de ejecutables PE32 [R.1]: DLLs, EXE...

- Win32 ASM [R.2]

- API y características de Microsoft Detours Library [R.6] y
EasyHook (parte unmanaged) [R.8] .

Se usarán los siguientes términos en el documento:

- hook_caller / API ID:

- Identificador del hook que llama a su handler.

- handler:

- Manejador de un hook.

- handler global / One hook handler / ooh / One safe hook handler:

- El handler al que llaman todos los hooks.


------[ 1.- Introducción

Existen varias bibliotecas para realizar hooks, sobre todo en Windows, pero
hace poco tuve que lidiar con un problema para el que no he encontrado
solución ni documentación al respecto. El problema es: ¿Cómo hacer hooks a
varias APIs, leyendo un fichero de configuración, en tiempo de ejecución,
el cual indica las APIs y sus prototipos? Comentado el problema, la
respuesta que recibí fue otra pregunta que quizá el lector ya se esté
haciendo, ¿para qué quieres hacer eso? La respuesta es: No quiero tener que
programar un handler diferente para cada API hookeada y tener que compilar
para que funcione, y tampoco quiero programar un creador de handlers en
tiempo de ejecución. Todos los comentarios se pueden reducir a dos
opciones:

1.- Programar en alguna tecnología que no requiera compilación previa.

2.- Programar algún tipo de macro para facilitar la programación y
además reducir la posibilidad de errores al compilar
.

Dado que las dos opciones no me convencieron, empecé a darle vueltas al
problema y qué era lo que realmente yo quería, y me surgió la pregunta
correcta: ¿Qué necesitaría para realizar lo que yo quiero? Y la respuesta
es simple: Teniendo un Handler para todos los hooks, saber cuándo es
llamado, qué API/hook lo ha hecho, y entonces actuar en consecuencia,
¡Exacto!, necesito un "API ID". O mejor dicho, necesito un "hook_caller
ID"
.

Es el momento de mencionar que el método de hook que necesito es "Detours"
[R.9] [R.6], es decir, meter un JMP, PUSH + RET ... en la dirección que se
desee meter el hook. Me decanté por dicho método [R.9] debido a un problema
que tienen algunos de los otros métodos como cuando se llama directamente a
la dirección de memoria donde está el hook, en cuyo caso no se ejecuta el
handler, ej IAT HOOKING. Los métodos que meten el JMP, PUSH + RET... en
memoria reservados o bytes de alineamiento ("padding bytes"), las llamadas
directas no ejecutan el handler. Dado que mi máxima prioridad era
interceptar todas las llamadas a las APIs donde haya hooks, el método
"Detours" [R.9] [R.6] que sobreescribe las instrucciones donde se quiere
introducir el hook con un JMP, me pareció el ideal. Aunque dicho método
conlleve usar algún tipo de LDE (Length-Disassembler Engine) [R.7], entre
otras cosas, hay bibliotecas en la red que permiten realizar este método en
Windows sin mayor dificultad.

Ahora ya solo queda responder a la pregunta: ¿Cómo puedo saber qué hook
llama al handler en tiempo de ejecución? Después de un rato meditándolo
tuve una idea feliz: Cambiar el método JMP por un método de tipo CALL, y
desde el handler comprobar la dirección de retorno que introduce el
CALL en la pila, usando dicha dirección de retorno como "hook_caller ID".
Dado que cada hook está en una posición de memoria diferente, cada CALL
introducirá un valor diferente en la pila, pudiendo servir éste de
identificador. Después, solo habría que modificar el método normal para que
el handler procesara dicho "hook_caller ID" y lo eliminara de la pila.

Problema resuelto. Ahora solo quedaba buscar un nombre, dicho nombre se me
ocurrió a la hora de programarlo: "E8 Method", pues dado un JMP (no SHORT)
en la dirección XXXXXXXX a YYYYYYYY, éste se codificará: "E9 ZZZZZZZZ", y
un CALL en la misma dirección a la misma dirección, se codificará: "E8
"ZZZZZZZZ". ¡Estaba claro!. Lo único que cambiaba era el opcode de la
instrucción, al ser el opcode de CALL, E8, decidí llamar al método "E8
Method"
. Pero "E8 Method", no solo es sustituir un hook de tipo JMP por un
hook de tipo CALL, es el concepto y/o la forma de implementar que un
handler global ("One hook handler") pueda obtener el "hook_caller ID" en
tiempo de ejecución.

Después de desarrollar la primera demostración de concepto (POC) tuve
varios problemas, como stack buffers overflows cuando se llamaban a APIs
hookeadas desde el hook handler de forma directa o indirecta,
comprobaciones del Microsoft Visual Studio para detectar cuando se corrompe
la pila usando el valor EDI que mi hook handler modificaba internamente
etc. Así que ya no solo tenía que crear un hook handler, tenía que crear un
safe hook handler que tuviera en cuenta la mayoría de los problemas.

A medida que el proyecto creció fue necesario programar partes en C/C++
resultando un código híbrido C/C++/ASM bastante problemático, por el cual
programé una capa de bajo nivel que permitía programar todo en C/C++ sin
preocupaciones. Además también surgieron stack buffers overflows al llamar
a un API hookeada desde el propio handler ya sea directa o indirectamente,
a lo que encontré una solución bastante más elegante que la mía en el
easy-hook llamada Thread Deadlock Barrier (TDB) [R.8].

Este documento trata de cómo usar e implementar el "E8 Method" con un solo
hook handler para todos los hooks que además será seguro y estará
programado en C/C++, "One safe hook handler". Se usarán dos bibiliotecas
públicas en las que se ha realizado un hack para dicho propósito:

1.- Microsoft Detours Library [R.6]

2.- Easy-Hook [R.8]


------[ 2.- Diseño

Como se ha mencionado antes: "E8 Method", no solo es sustituir un hook de
tipo JMP por un hook de tipo CALL, es el concepto y/o la forma de
implementar que un handler global ("One hook handler") pueda obtener el
"hook_caller ID" en tiempo de ejecución". Puede haber muchos escenarios y
formas de plasmar E8 Method, en el documento se seguira el siguiente
contexto:

1) Escenario: Dado un hook de tipo Detours covencional se modificará
para poder obtener el "hook_caller ID".

2) Forma: Se cambiará el salto al hook handler de tipo JMP por un salto
de tipo CALL, y se reparará la pila para que todo funcione
como en el método Detours convencional.

Nota: Para simplificar el documento se asume que el area donde se introduce
el hook es siempre una API, o mejor dicho un área en la que se ha entrado
mediante un CALL del código original.


------[ 2.1 - Método Detours convencional

El método Detours convencional se basa en introducir un hook
sobreescribiendo la/s primera/s instrucción/es original/es con un JMP al
hook handler programado para ese hook. Cuando se hace un hook también se
crea un area en memoria llamada "trampolín" la cual tiene las instrucciones
sobreescritas por el JMP y un salto a la siguiente instrucción no
sobreescrita del código original. El trampolín es usado por el hook handler
el cual usa si desea ejecutar al area original donde se ha introducido el
hook. Para simplificar el documento se asume que el area donde se introduce
el hook es siempre una API, o mejor dicho un área en la que se ha entrado
mediante un CALL del código original.


------[ 2.1.1 - Flujo

El flujo que sigue el método Detours convencional puede ser de dos tipos:

1) Sin usar el trampolín desde el hook handler:

a) Se ha introducido un hook de tipo Detour en la API Sleep de
kernel32.dll

b) El programa original (POC.exe) llama a Sleep

c) La primera instrucción de Sleep es una salto hacia el hook
handler para dicha API.

d) El hook handler realiza las acciones para las que ha sido
programado y retorna al programa original como lo haría la API
Sleep.

1) Usando el trampolín desde el hook handler:

a) Se ha introducido un hook de tipo Detour en la API Sleep de
kernel32.dll

b) El programa original (POC.exe) llama a Sleep

c) La primera instrucción de Sleep es una salto hacia el hook
handler para dicha API.

d) El hook handler realiza las acciones para las que ha sido
programado, llamando en un determinado momento a la API Sleep
original usando el tramplín. Finalmente retorna al programa
original como lo haría la API Sleep.

ASCII ART del flujo sin usar trampolín desde el hook handler:

+- POC.EXE --+ 1 +------ Sleep -----+ 2 + - hook_handler - +
| CALL Sleep | ---> | JMP hook_handler | --> | ANYTHING :-) | 3
+->| ... | | .... | | RET | ---+
| +------------+ | RET | +------------------+ |
| +------------------+ |
| [Trampolín (sin usar)] |
| 4 |
+------------------------------------------------------------------------+

ASCII ART del flujo usando trampolín desde el hook handler:

6
+---------------------------------------------------------------------+
| |
| +- POC.EXE --+ 1 +------ Sleep -----+ 2 + - hook_handler - + |
| | CALL Sleep | ---> | JMP hook_handler | --> | ANYTHING :-) | |
+->| ... | +->| .... | 5 | CALL Trampolin |-|--+
+------------+ | | RET | --> | RET |-+ |
| +------------------+ +------------------+ |
4 | |
+------------------+ |
| |
| +---- Trampolin -----+ 3 |
| | - Instrucciones | <--------------------------------------------+
| | sobreescritas |
+- | - JMP Sleep+X |
+--------------------+


------[ 2.1.2 - Stack

Para entender las diferencias entre el E8 Method y el método Detour
convencional es necesario entender los diferentes estados de la pila en
cada pasa del Detour.

En los siguientes ASCII ART el cuadrado Stack muestra siempre en la cima el
valor de la cima de la pila (ESP) en cada paso.

Pasos:

1) El programa original (POC.exe) llama a Sleep, se introduce la dirección
de la siguiente instrucción al CALL para volver con la instrucción RET:

Tags:
+- POC.EXE --+ 1 +---- Stack ----+
| CALL Sleep | ---> | Address of LE |
LE: | ... | | .... |
+------------+ | .... |
| .... |
| .... |
+---------------+

2) La primera instrucción de Sleep es un salto hacia el hook handler para
dicha API, al ser un JMP la pila queda intacta:

Tags:
+------ Sleep -----+ 2 +---- Stack ----+
| JMP hook_handler | --> | Address of LE |
| ... | | .... |
+------------------+ | .... |
| .... |
| .... |
+---------------+
3) El hook handler realiza las acciones para las que ha sido programado y
retorna al programa original como lo haría la API, sacando la dirección
de retorno de la pila.
Sleep.
Tags:
+ - hook_handler - + +---- Stack ----+
| ANYTHING :-) | 3 | Address of LE |
| RET | ---> | .... |
+------------------+ | .... |
| .... |
| .... |
+---------------+

Nota: En el ejemplo no se ha ilustrado el uso del trampolín, pero sería
como un CALL y RET normal.


------[ 2.2 - E8 Method

Al contrario que el método Detour convencional, E8 Method se basa en
introducir un hook sobreescribiendo la/s primera/s instrucción/es
original/es con un CALL al hook handler programado para todos los hooks.
Cuando se hace un hook también se crea un area en memoria llamada
"trampolín" igual a la del método Detour convencional, la cual tiene las
instrucciones sobreescritas por el CALL y un salto a la siguiente
instrucción no sobreescrita del código original. El trampolín es usado por
el hook handler el cual usa si desea ejecutar al area original donde se ha
introducido cualquiera de los hook. Para simplificar el documento se asume
que el area donde se introduce el hook es siempre una API, o mejor dicho
un área en la que se ha entrado mediante un CALL del código original.

Además el hook handler debe reparar la pila para que no quedé la dirección
de retorno introducida por el CALL que se usa como hook caller id.


------[ 2.2.1 - Flujo

El flujo que sigue E8 Method puede ser de dos tipos al igual que el Detours
convencional:

1) Sin usar el trampolín desde el hook handler:

a) Se ha introducido un hook de tipo E8 Method en la API Sleep de
kernel32.dll

b) El programa original (POC.exe) llama a Sleep

c) La primera instrucción de Sleep es un CALL hacia el hook
handler
para todos los hooks.

d) El hook handler realiza las acciones para las que ha sido
programado y retorna al programa original como lo haría la API
Sleep
en este caso.

1) Usando el trampolín desde el hook handler:

a) Se ha introducido un hook de tipo Detour en la API Sleep de
kernel32.dll

b) El programa original (POC.exe) llama a Sleep

c) La primera instrucción de Sleep es un CALL hacia el hook
handler
para todos los hooks.

d) El hook handler realiza las acciones para las que ha sido
programado, llamando en un determinado momento a la API Sleep
original usando el trampolín. Finalmente retorna al programa
original como lo haría la API Sleep en este caso.

ASCII ART del flujo sin usar trampolín desde el hook handler, E8 Method:

+- POC.EXE --+ 1 +- Sleep --+ 2 + --- ooh ---- +
| CALL Sleep | ---> | CALL ooh | --> | ANYTHING |
+->| ... | | .... | | stack_repair | 3
| +------------+ | RET | | RET |----+
| +----------+ +--------------+ |
| [Trampolín (sin usar)] |
| 4 |
+------------------------------------------------------------+

ASCII ART del flujo usando trampolín desde el hook handler, E8 Method:

6
+---------------------------------------------------------------------+
| |
| +- POC.EXE --+ 1 +------ Sleep -----+ 2 + ------ ooh ----- + |
| | CALL Sleep | ---> | CALL ooh | --> | ANYTHING :-) | |
+->| ... | +->| .... | 5 | stack_repair | |
+------------+ | | RET | --> | CALL Trampolin |-|--+
| +------------------+ | RET |-+ |
4 | +------------------+ |
+------------------+ |
| |
| +---- Trampolin -----+ 3 |
| | - Instrucciones | <--------------------------------------------+
| | sobreescritas |
+- | - JMP Sleep+X |
+--------------------+


------[ 2.2.2 - Stack

En E8 Method la pila no se comporta del mismo modo que en el método Detours
convencional, en ella se introduce el hook caller ID que es la dirección
de retorno de un CALL.

En los siguientes ASCII ART el cuadrado Stack muestra siempre en la cima el
valor de la cima de la pila (ESP) en cada paso.

Las etapas de la pila son:

1) El programa original (POC.exe) llama a Sleep, se introduce la dirección
de la siguiente instrucción al CALL para volver con la instrucción RET:

Tags:
+- POC.EXE --+ 1 +---- Stack ----+
| CALL Sleep | ---> | Address of LE |
LE: | ... | | .... |
+------------+ | .... |
| .... |
| .... |
+---------------+

2) La primera instrucción de Sleep es un CALL hacia el hook handler para
dicha API, al ser un CALL se introduce en la pila la dirección de la
siguiente instrucción:

Tags:
+-- Sleep -+ 2 +---- Stack ----+
| CALL ooh | --> | Address of LO |
LO: | ... | | Address of LE |
+----------+ | .... |
| .... |
| .... |
+---------------+

3) En esta etapa ooh usa " Address of LO" como "hook_caller ID" y luego
lo saca de la pila para que pueda continuarse el flujo normal como en
el método Detour convencional:

Tags:
+ ---- ooh ---- + +---- Stack ----+
| ANYTHING :-) | | Address of LE |
| stack_repair | 3 | .... |
| RET | ---> | .... |
+---------------+ | .... |
| .... |
+---------------+

Nota: En el ejemplo no se ha ilustrado el uso del trampolín, pero sería
como un CALL y RET normal.


------[ 2.3 - Diferencias

Solo existen tres diferencias entre el método Detours convencional y E8
Method
:

1) En vez de un salto normal al hook handler se usa uno de tipo CALL.

2) En vez de un hook handler para cada hook se usará el mismo hook
handler
para todos los hooks.

3) Se debe extraer el hook caller ID introducido por el CALL que llama
al hook handler de la pila para que no moleste y pueda ser todo
como en el método Detours convencional.


------[ 2.4 - Safe Hook Handler

He definido Safe Hook Handler como un hook con dos características, debe
ser:

1) Invisible para el código legítimo: no alterar flags ni registros,
no ser localizable usando estructuras internas del SO...

2) Evitar problemas de seguridad: sin stack buffers overflows al llamar
desde el hook handler a un area hookeada...


------[ 2.4.1 - Siendo invisible

Para conseguir o intentar ser invisible para el código legítimo, es
necesario tener en cuenta:

1) Evidencias de que existe un hook basadas en la arquitectura del
microprocesador
: registros, flags ...

2) Evidencias de que existe un hook no basadas en la arquitectura del
microprocesador
. Por ejemplo, un hook handler en Windows
implementado en una DLL, para evitar una búsqueda usando el PEB, se
podría desenlazar la DLL de la lista LDR_MODULE.


------[ 2.4.1.1 - Evidencias basadas en el microprocesador

Puede ser útil guardar toda la información relativa al estado del
microprocesador una vez es llamado el hook handler la cual será restaurada
una vez se devuelva el control al código original.

Dos de las cosas más importantes a ser guardadas son los registros del
microprocesador y el estado de los flags. Un ejemplo real es cuando el
Microsoft Visual Studio introduce la comprobación de corrupción de pila,
siendo una llamada a Sleep de kernel32.dll en ensamblador:

Código: Seleccionar todo

    MOV ESI,ESP
    PUSH 1388
    CALL DWORD PTR DS:[<&KERNEL32.Sleep>]    
    CMP ESI,ESP
En un entorno sin hook, la implementación de Sleep no debería modificar el
valor de ESI. Si introducimos un hook en Sleep que modifica ESI y no se
restaura al valor original antes de devolver el control al código original
el programa causará una excepción abortando la ejecución.

Se podrían encontrar algún sistema de protección de software o malware que
aproveche ciertos flags cuando se llama a una API o registros, de tal forma
que si nuestro hook handler no lo hace igual será detectado.

Así que tenemos dos problemas y dos soluciones posibles:

1º Problema: Comportamiento del API legítima usando como comprobación
registros o flags del microprocesador que no modifique.

Solución: Cuando es llamado el hook handler se deben guardar todos los
flags y registros que serán restaurados cuando se devuelva el
control al código original.

2º Problema: Comportamiento del API legítima usando como comprobación
registros o flags del microprocesador que modifique.

Solución: El hook handler debe llamar a la API original con los
registros y flags iguales a los del código original, después
de la llamada se debe guardar todos los flags y registros que
serán restaurados cuando se devuelva el control al código
original. En x86 sería suficiente con un PUSHAD y un PUSHFD
cuando se llame al hook handler, cuando se llama a la API
un POPFD y un POPAD y después de la llamada otro PUSHAD y
PUSHFD, por último, cuando se devuelva el control al código
original de nuevo hacer otro POPFD y POPAD de los valores que
devolvió la llamada a la API.

El primer problema es fácil de solucionar, solo es necesario crear un
buffer con los flags y los registros cuando el hook handler es llamado sin
mayores consecuencias, sin embargo el segundo problema conlleva mayores
consecuencias, debido a que debemos ejecutar la API como originalmente lo
haría el código original y eso puede no ser conveniente. Otra posible
solución es analizar como se comporta internamente la API y virtualizar
las evidencias de una llamada con los argumentos del código original.

Solución al primer problema, dado el caso real del Visual Studio mencionado
anteriormente:

Tags:
+- POC.EXE ----+
| MOV ESI, ESP |
| PUSH 0 | 1 +---- Sleep ----+ 2 +- ooh ------+
| CALL Sleep | ---> | CALL ooh | --> | PUSHAD |
LE: | CMP ESI, ESP | <-+ | .... | | PUSHFD |
+--------------+ | | .... | | ADD ESI, 9 |
| | .... | | POPFD |
| | .... | | POPAD |
| +---------------+ +-- | RET TO LE |
| 3 | +------------+
+-----------------------+

Para ilustrar el segundo problema imaginemos que cuando se hace una llamada
verdadera a Sleep con EAX = 69, se devuelve en EDX el valor 7C91E4F4, como
virtualizar las evidencias puede ser tedioso, el siguiente ASCII ART
muestra la opción de llamar a la rutina original:

Tags:
+- POC.EXE ----+
| MOV EAX, 69 |
| PUSH 0 | 1 +---- Sleep ----+ 2 +- ooh ------+
| CALL Sleep | ---> | CALL ooh | --> | PUSHAD |
LE: | ... | <-+ | .... | | PUSHFD |
+--------------+ | | .... | | MOV EAX, 0 |
| | .... | | POPFD |
| | .... | | POPAD |
| +---------------+ | CALL Sleep |
| | PUSHAD |
| | PUSHFD |
| | ANYTHING |
| | POPFD |
| | POPAD |
| +-- | RET TO LE |
| 3 | +------------+
+-----------------------+

Nota: El CALL Sleep de ooh es una llamada a la rutina original (trampolín).

La mejor solución es la segunda ya que simulamos una llamada a la API
original que es lo que haría el programa originalmente, la primera solución
solo vale en caso de que no se base en comportamientos internos.


------[ 2.4.1.2 - Evidencias no basadas en el microprocesador

Pueden ser muchas y de muchas maneras, dependen del Sistema Operativo, a
modo de ejemplo listaré algunas directicres de forma genérica:

1) En caso de que el hook handler se encuentre en una bibilioteca:

a) Borrar entrada de la biblioteca en listas que se creen en
runtime con la biblioteca, sin que deje la biblioteca de
funcionar.
b) Si se lee la memoria página a página, ocultar de alguna forma
las páginas donde se encuentre la biblioteca.
c) .......
z) ..........
2) .................
99) ................


------[ 2.4.2 - Problemas de seguridad

Los principales problemas de seguridad se pueden divir en dos:

1) Llamada a una API hookeada - stack buffer overflow.

2) Otros problemas debidos a mala programación del hook handler.


------[ 2.4.2.1 - Llamada a una API hookeada

Si desde el hook handler se llama a una API hookeada se entrará en un bucle
infinito que causará un stack buffer overflow. Hay tres formas de afrontar
el problema:

1) No llamar en el hook handler a APIs hookeadas.

a) Si se desea por ejemplo poner hooks a todas las APIs del sistema
no se podrá llamar a ninguna, dado que se quiere decidir los
hooks en runtime ésta solución no es válida. Tampoco se podrá
llamar a otras APIs que a su vez llamen a APIs hookeadas o
similar.

2) Llamar al trampolín de las APIs hookeadas directamente.

a) Exige controlar que APIs han sido hookeadas y cuales no. Por
ejemplo en C/C++ se requeriría un array con las APIs a usar,
que será al que siempre se llame, si se pone en hook en dicha
API se deberá modificar el valor con el del trampolín. No se
podrá llamar a otras APIs que a su vez llamen a APIs hookeadas,
solo se podrá usar el trampolín.

3) Crear un mecanismo que sea capaz de detectar cuando se llame a una
API hookeada si ha sido el hook handler, en cuyo caso se llamara al
API original o no, en cuyo caso se llamará al hook handler. Si esto
se consigue se podrá llamar directamente a las APIs originales sin
mayor preocupación, un ejemplo de ésta idea es el TDB (Thread
Deadlock Barrier)del easy-hook
[R.8].

Las dos primeras soluciones exigen una metodología a la hora de programar
el hook handler, siendo la primera en muchos casos no viable debido a que
habría que programar demasiado, en la segunda alternativa puede pasar lo
mismo si se desea usar alguna biblioteca de alto nivel que podría llamar a
un API hookeada. Así pues la tercera alternativa permite programar al hook
handler
como se desee, sin preocuparse si se llama a un API hookeada o a
otra biblioteca que llame a su vez a un API hookeada.

Ejemplo ASCII ART de la tercera alternativa:

4
+------------------------------------------+
Tags: | |
| +- POC.EXE ----+ |
| | PUSH 0 | 1 +- Sleep ----+ 2 | +- ooh ------+
| | CALL Sleep | ---> | CALL check | -+ | | CALL check |
LE: +>| CMP ESI, ESP | | .... | | +-| RET TO LE |<-+
+--------------+ | .... | | +------------+ |
| .... | | |
| .... | | |
+------------+ | |
| 3 |
| |
+----------------------------+ |
| +------------- check ----------------------+ |
+->| If (caller == ooh) CALL Sleep_Trampolin | |
| else CALL ooh |---+
+------------------------------------------+

check para que funcione de forma ideal no solo tiene que leer el valor que
introduce el CALL para saber de donde viene, es necesario que tenga algún
mecanismo tipo "marca" por cada hilo, ejemplo: un hilo ha llamado a una API
hookeada
, si ya está marcada esa API para ese hilo se llama a la API
original
, en caso contrario, se marca la API y se llama al hook handler,
una vez se haya ejecutado el hook handler se quita la marca la API. Esto
solo es un ejemplo para mostrar la idea, a la hora de implementar la idea
se debe tener en cuenta cualquier tipo de condición de carrera.


------[ 2.4.2.2 - Mala programación hook handler

Hacer el hook handler en ensamblador puede no ser viable, queriendo usar
tecnologías como C/C++. El problema de hacerlo en ensamblador es que se
necesitará más código y quizá haya más errores que vuelvan inestable el
hook teniendo en cuenta todo lo que implica un Safe Hook Handler. Por otro
lado al programar el hook handler en C/C++ no se puede controlar de forma
ANSI / ISO lo que pasa a bajo nivel (inline assembly) y en caso de que se
pudiera el código se puede volver no mantenible. Las posibles soluciones
para este problema son:

1) Programar todo en ensamblador teniendo mucho cuidado.

2) Programar en C/C++ usando inline assembly o enlazando con objetos
creados en ensamblador para ajustes de pila y otros asuntos de bajo
nivel necesarios para un hook.

3) Crear una pequeña parte en ensamblador que antes de llamar al hook
handler guarde todo lo necesario para que el hook handler pueda
obtener los datos necesarios (hook caller id, argumentos...) y que
una vez se ejecute el hook handler programado en C/C++ con la
información guardada al principio se pueda reajustar todos los
parámetros para un correcto funcionamiento del programa original.

El método más recomendable es el tercero, ya que el hook handler se abstrae
de la parte de bajo nivel y puede hacer lo que necesite sin preocuparse
por la pila y otros asuntos.

Ejemplo de la tercera solución:

1) Se creará una estructura con los datos necesarios para restaurar el
flujo normal una vez acabada la ejecución del hook handler, también se
encontrará en dicha estructura la información de bajo nivel necesaria, como
dónde están los argumentos en la pila.

Un ejemplo de estos datos podrían ser:

1) Estado de los registros y flags del microprocesador, cosa que ya
se hacía para otro propósito que cuenta el capítulo "2.4.1.1 -
Evidencias basadas en el microprocesador"
. Gracias a esto nuestro
hook handler programado en C/C++ podrá alterar los registros y los
flags del microprocesador sin preocupaciones.

2) Dirección de donde se encuentran los argumentos en la pila, para que
el hook handler programado en C/C++ pueda obtenerlos.

3) El hook_caller ID para saber que API ha llamado al hook handler y
cuantos argumentos debe obtener de la pila.

4) Dónde se encuentra la dirección de retorno al código original.

La estructura en C podría ser algo así:

Código: Seleccionar todo

    typedef struct handle_ooh_s
    {
        void * hook_caller_id;
        void * registers_and_flags_saved;
        void * args;
    
    } handle_ooh_t;

Nota: El retorno al código original estará siempre antes del hook_caller_id
en memoria debido a que antes del CALL del E8 Method, está la dirección de
retorno introducido por el CALL del código original.

Asumiendo que la pila cuando se llama al hook handler está en la cima con
el hook_caller ID (E8 Method), y después los argumentos de la pila, ejemplo
de la creación de la estructura:

Tags:
+-- Sleep -----+ 1 +--- ooh_asm --------------------+
| CALL ooh_asm | --> | PUSHAD |
LO: | ... | | PUSHFD |
+--------------+ | PUSH ESP_BEFORE_HOOK_CALLER_ID |
| PUSH_ESP_BEFORE_PUSHAD |
| PUSH HOOK_CALLER_ID |
| PUSH ESP |
| PUSH GARBAGE | 2
| JMP ooh | ---> ...
+--------------------------------+

Quedando en la cima de la pila un valor basura que simula un retorno para
el hook handler programado en C/C++ y después la dirección de memoria donde
se encuentra la estructura de bajo nivel.

2) El hook handler que tendrá un prototipo similar a "void hook_handler(
handle_ooh_t * handle )"
, usará unas interfaces programadas en ensamblador
para obtener la información necesaría del handle: obtener los argumentos,
el hook_caller ID etc.

Ejemplo de obtención del hook_caller ID:

Tags:
+-- ooh --------------------+ 1 +--- ooh_asm --------------------+
| GetHookCallerID( handle ) | --> | MOV EAX, handle.hook_caller_id |
LO: | ... | <-- | RET |
+---------------------------+ +--------------------------------+

Ejemplo de obtención de los argumentos para una API (concepto -
pseudocódigo)
:

+-- ooh -----------------------------------+ +--- ooh_asm -------------+
| id = GetHookCallerID( handle ) | | ... |
| GetArgs( handle, API[id].nr_args, args ) |-> | while ( i < nr_args ) |
+------------------------------------------+ | args = handle.args|
| ... |
| RET |
+-------------------------+

3) El retorno al código original desde el hook handler también se debe
hacer desde una interfaz de bajo nivel que obtenga los valores del PUSHAD y
PUSHFD y retorne como si no se hubiera llamado al hook handler. Esto
también se hace en "2.4.1.1 - Evidencias basadas en el microprocesador"
pero con otra motivación, además de hacerse también en el CALL a la API
original con otra motivación.

Ejemplo de retorno del hook handler desde C/C++:

+-- ooh --------+ +--- ooh_asm ---------------------------------+
| ... | | ... |
| Ret( handle ) |-> | MOV ESP, handle.registers_and_flags_saved |
+---------------+ | POPFD |
| POPAD |
| ADD ESP, 4 |
| RET |
+---------------------------------------------+

Nota: Se hace un ADD ESP, 4 por que justo antes del PUSHAD en memoria
estaba el hook_caller ID (E8 Method) y antes el valor de retorno al código
original.


------[ 2.5 - Conclusión

Para llevar acabo el E8 Method con un Safe Hook Handler, serán necesarios
los siguientes elementos:

1) Biblioteca Detour adaptada a E8 Method para crear un solo hook
handler para todos los hooks.
( Capítulo 2.1 y 2.2 ).

2) Una capa en ensamblador con dos propósitos:

a) Permitir programar el hook handler en un lenguaje que no sea
ensamblador como C/C++. ( Capítulo 2.4.2.2 ).

b) Evitar métodos de detección de hooks o anomalías, como el
MOV EDI, ESP del visual studio para detectar corrupciones de
pila (basados en la arquitectura del microprocesador).
( Capítulo 2.4.1.1 ).

3) Método para ocultar evidencias de que existe un hook handler que
no tengan que ver con la arquitectura del microprocesador

( Capítulo 2.4.1.2 ).

4) Método para evitar stack buffers overflows y otros errores cuando
se llama desde el hook handler a una API ya hookeada
. ( Capítulo
2.4.2.1 ).
Imagen


Solo lo mejor es suficiente...
------[ 3.- Implementación

Se han realizado dos implementaciones del E8 Method:

1) Microsoft Detours Library:

a) Capa en ensamblador con las características:

a.1) Evidencias basadas en el microprocesador:

a.1.1) Registros: Una vez se ejecuta el CALL al hook
handler
se guardan los registros con PUSHAD.

a.1.2) Flags: Una vez se ejecuta el CALL al hook
handler
se guardan los registros con PUSHFD.

a.2) Restauración de la pila: Se guarda ESP en la llamada al
hook handler para poder extraer los argumentos de forma
segura y reestablecer ESP una vez se ha ejecutado el hook
handler
.

a.3) Llamada a una API hookeada de forma segura: Se restauran
los registros y los flags guardados anteriormente y se
llama a la API hookeada como si se tratara del código
original. Después de la llamada se guardan de nuevo los
registros y los flags.

a.4) Retorno seguro: Se restauran los registros y los flags
de la última llamada a una API hookeada de forma segura o
si no se ha hecho, se restauran los registros y los flags
de la llamada al hook handler. Por último se restaura la
pila y se retorna al código original.

a.5) Argumentos de la API hookeada: se pueden obtener los
argumentos de la API original desde el hook handler.

a.6) hook_caller ID: se puede obtener el hook_caller ID, el
cual es la dirección en memoria de la API original.

b) Llamada a una API hookeada: todas las APIs externas que se usen
se crean como punteros a función globales, los cuales están en
una tabla que se inicia una vez se introducen los hooks, si hay
algun elemento de la tabla hookeado se reemplaza la dirección
por el trampolín para evitar stack buffers overflows.

c) C/C++: Se puede programar en C/C++ con total libertad siguiendo
unas directivas:

c.1) No se podrá llamar a módulos que usen APIs hookeadas, así
que solo se deberían usar las APIs directamente. Tampoco
se podrán usar APIs que llamen a su vez a otras APIs
hookeadas
. Estos problemas los resuelve el TDB del
Easy-Hook
[R.8].

c.2) Se podrá llamar a la API hookeada usando los punteros
globales a las APIs directamente, pero para llamar con los
mismos registros y mismos flags que el código original se
usará la capa de bajo nivel.

c.3) Se retornará al código original con la capa de bajo nivel.
un return de C/C++ en el hook handler no se puede hacer
NUNCA, la capa de bajo nivel restaura la pila, los flags y
los registros del microprocesador y sabe dónde retornar.

c.4) Para obtener el hook_caller ID y los parámetros del código
original se usará la capa de bajo nivel.

c.5) Se usará el prototipo One Hook Handler para poder usar el
handler de bajo nivel con las interfaces.

c.6) Se usarán las APIs añadidas a la biblioteca Detours
original
para introducir saltos CALL en vez de JMP.

d) Se han añadido APIs a la biblioteca Detours original para
permitir hacer hook de tipo CALL usando una sintaxis
parecida al ATTACH de la biblioteca Detours. El hack es muy
simple y BETA, solo se garantiza el funcionamiento de
introducir CALLs y la creación del trampolín.

2) Easy-hook: se ha usado la API unmanaged y el hook handler también es
unmanaged [R.12].

a) Capa en ensamblador con las características:

a.1) Registros: Una vez se llega al hook handler se guardan
los registros con PUSHAD. Los registros no
llegan al hook handler tal como estaban en la
llamada del código original, esto se debe a
que se ejecuta el TDB antes del hook handler.

a.2) Flags: Una vez se llega al hook handler se guardan
los flags con PUSHFD. Los flags no llegan siempre
al hook handler tal como estaban en la llamada
del código original, esto se debe a que se
ejecuta el TDB antes del hook handler y puede
alterar los flags.

a.3) Restauración de la pila: Se guarda ESP en la llamada al
hook handler para poder extraer los argumentos de forma
segura y reestablecer ESP una vez se ha ejecutado el hook
handler
.

a.4) Llamada a una API cualquiera de forma segura: Se restauran
los registros y los flags guardados anteriormente y se
llama a la API como si se hiciera en la primera
instrucción del hook handler. Después de la llamada se
guardan de nuevo los registros y los flags.

c.5) Se retornará al código original con la capa de bajo nivel.
un return de C/C++ en el hook handler no se puede hacer
NUNCA, la capa de bajo nivel restaura la pila, los flags y
los registros del microprocesador y sabe dónde retornar.

a.6) Argumentos de la API hookeada: se pueden obtener los
argumentos de la API original desde el hook handler.

c.7) Para obtener el hook_caller ID y los parámetros del código
original se usará la capa de bajo nivel. Se usa el
mecanismo Callback del easy-hook para obtener los
hook_caller ID.

b) Llamada a una API hookeada: el TDB del easy-hook permite llamar
directamente a una API hookeada sin
preocuparse por trampolines. El TDB
también permite llamar a APIs no
hookeadas
que llamen a su vez a APIs
hookeadas (sin importar el nivel de
profundidad)
.

c) C/C++: Se puede programar en C/C++ con total libertad siguiendo
unas directivas:

c.1) Se retornará al código original con la capa de bajo nivel.
un return de C/C++ en el hook handler no se puede hacer
NUNCA, la capa de bajo nivel restaura la pila, los flags y
los registros del microprocesador y sabe dónde retornar.

c.2) Para obtener el hook_caller ID y los parámetros del código
original se usará la capa de bajo nivel.

c.3) Se usará el prototipo One Hook Handler para poder usar el
handler de bajo nivel con las interfaces.


------[ 3.1- Microsoft Detours Library

En los siguientes capitúlos se detallarán las funcionalidades citada en el
capítulo 3. Tambien se explicará un POC creado para usar el hack a la
Detours Library - E8 Method.


------[ 3.1.1- Capa en ensamblador

La capa en ensamblador siempre necesita como parámetros el handle_ooh que
es creado una vez se ejecuta el CALL del hook, la estructura es la
siguiente:

Código: Seleccionar todo


    typedef struct handle_ooh_s
    {
        void * hook_caller_id;
        void * esp_args;
        void * esp_restore_context;
    
    } handle_ooh_t;

Descripción de los parámetros:

1) hook_caller_id: Entry-point de la función hookeada, que será el
hook_caller_id.

2) esp_args: Dirección donde está el retorno al código original y los
argumentos de la llamada original: esp_args.

3) esp_restore_context: Dirección donde están los flags y registros
salvados.

En los siguientes capitúlos se tratarán las interfaces de bajo nivel y la
creación del handle_ooh.


------[ 3.1.1.1- Creación handle_ooh

Todos los hooks se dirigen al mismo hook handler, antes de ejecutarse el
hook handler en C/C++ se ejecuta un pequeño código en ensamblador que crea
el handle_ooh citado 3.1.1, handle_ooh se usará desde hook handler C/C++
para las tareas de bajo nivel. Éste código es lo primero que se ejecuta una
vez se ejecuta el CALL del hook:

Código: Seleccionar todo


    // Cuando se ejecuta este código en la pila se encuentra empezando por 
    // la cima: 
    //     1) 4h bytes, Dirección de retorno del hook CALL.
    //     2) 4h bytes, Dirección de retorno al código original.
    //     3) X  bytes, Argumentos de la llamada original. 
    
    PUSHAD // Se guardan los registros como estaban en el CALL.
    PUSHFD // Se guardan los flags como estaban en el CALL.
    
    MOV EBX,DWORD PTR SS:[ESP+24h] // EBX = Dirección de retorno del hook 
                                   // CALL.
                                   // 24h =  4h bytes (PUSHFD)   + 
                                   //       20h bytes (PUSHAD)  

    SUB EBX, 5h // Se resta el tamaño de la instrucción CALL para tener el
                // entry-point de la función hookeada, que será el 
                // hook_caller_id.
    
    PUSH ESP     // Se introduce en la pila la dirección donde están los 
                 // flags y registros salvados, tercer campo de handle_ooh:
                 // esp_restore_context.

    MOV EAX, ESP 
    ADD EAX, 2Ch // Se le suma 0x2C al valor de EAX para tener la dirección
                 // donde está el retorno al código original y los 
                 // argumentos de la llamada original: esp_args.
                 // 0x2C =   4h bytes (esp_restore_context)   + 
                 //          4h bytes (PUSHFD)                + 
                 //          20h bytes (PUSHAD)               + 
                 //          4h bytes (retorno del hook CALL) 

    PUSH EAX // Se introduce la dirección calculada en EAX para crear el 
             // segundo campo de handle_ooh: esp_args.
    
    PUSH EBX // Se introduce en la pila el primer campo de handle_ooh:
             // hook_caller_id.

    PUSH ESP // Se pone la dirección donde empieza el handle_ooh en memoria
             // que será el único parámetro que reciba el hook handler 
             // programado en C/C++.
    
    PUSH ECX // Se introduce un valor basura en la pila que sustituye a la
             // dirección de retorno para el hook handler en C/C++, no está
             // soportado el retorno al código original directamente.
    
    JMP one_hook_handler // Se salta al hook handler programado en C/C++.

Nota: es posible que se pueda mejorar el código, es solo una versión BETA
funcional.


------[ 3.1.1.2- SafeCall

Permite hacer llamar a la API hookeada de forma segura:

1) Se restauran los registros y los flags guardados antes de entrar al
hook handler C/C++.

2) Se llama a la API hookeada como si se tratara del código original.

3) Después de la llamada se guardan de nuevo los registros y los flags.
para poder usar con el SafeRet o en otro SafeCall.

El prototipo de SafeCall:

- int SafeCall( int, handle_ooh_t *, void *, void * ):

- 1º param: Número de argumentos con el que se hará la llamada
segura. (Unidad int).

- 2º param: handle_ooh.

- 3º param: Dirección al que hacer el CALL, en caso de hacerse a
una función hookeada se debe usar el trampolín.

- 4º param: Dirección donde se encuentran los parámetros para hacer
la llamada.

- Retorno: Retorno del sitio al que se ha hecho el CALL (se
preserva EAX después de hacer el CALL
).


------[ 3.1.1.3- SafeRet

Permite hacer un retorno seguro al código original:

1) Se restauran los registros y los flags de la última llamada a una
API hookeada de forma segura o si no se ha hecho, se restauran los
registros y los flags de la creación del handle_ooh.

2) Por último se restaura la pila y se retorna al código original.

El prototipo es:

- void SafeRet( int, handle_ooh_t *, void * ):

- 1º param: Número de parámetros de la llamada original de la API.
El handle_ooh guarda donde empieza el retorno al
código original seguido de los parámetros originales,
pero aún quedan los parámetros en la pila que según el
convenio de llamadas habrá que cambiar la cima de la
pila o no, para eso sirve éste parámetro, para reparar
la pila usando como unidad int.

- 2º param: handle_ooh.

- 3º param: Dirección donde se encuentra valor que tendrá EAX al
retornar al código original, si se indica NULL será
restaurado el valor de los registros salvados.


------[ 3.1.1.4- Otras funciones de bajo nivel

Otras funciones de la capa de bajo nivel que se pueden usar desde el hook
handler C/C++
:

- void * GetHookCallerID( handle_ooh_t * ):

- 1 param: handle_ooh.

- Retorno: El hook_caller_id del handle_ooh. Este valor es el entry
point de la API hookeada.

- int GetArgs( handle_ooh_t *, int, int * ):

- 1º param: handle_ooh.

- 2º param: Número de parámetros. (Unidad int).

- 3º param: array int de tamaño 2º param, que será iniciado usando
el campo esp_args de handle_ooh. Tener en cuenta que
esp_args apunta a la dirección de retorno al código
original y después se encuentran los argumentos
originales.

- Retorno: -1 si ha ocurrido algún error, en caso contrario -1.


------[ 3.1.2 - one_hook_handler

El prototipo del hook handler global C/C++ debe ser:

- void one_hook_handler( handle_ooh_t * );

Se deberán usar las directivas citadas en el capítulo 3.

one_hook_handler es un POC de un API Spy muy beta y muy básico,
funcionalidades:

1) Dispone de una tabla en la que se debe indicar:

a) DLL donde está la API a hookear.

b) API a hookear.

c) ID: este campo debe ser 0, será iniciado cuando se introduzcan
los hooks y corresponderá con el hook_caller ID del
handle_ooh.

d) Tram: este campo debe ser 0, será iniciado cuando se introduzcan
los hooks y corresponderá con el trampolín de la API
hookeada
.

e) To log: Que parámetros se deben loggear y de que tipo son:

e.1) DWORD: Tipo de dato DWORD que será mostrado con un printf
%d
.

e.2) STRING: Dirección de memoria donde hay una string tipo C,
será mostrado con un printf %s.

f) Número de parámetros de la API (unidad int).

g) En caso de que sea necesario ejecutar la API original, con los
parámetros originales se debe indicar true, en caso contrario
false.

2) Se realizarán las acciones en el one hook handler indicadas en la
tabla y se mostrará un pequeño debug por la consola.

Tabla para introducir los hooks, se podría crear leyendo un fichero de
configuración y no directamente sobre el código:

Código: Seleccionar todo


    // dll          | func   | id     |tram| To log     | params | ¿orig? 
	{ "kernel32.dll", "Sleep",       0,   0,  "1-DWORD;",         1, true }
	{ "user32.dll",   "MessageBoxA", 0,   0,  "1-DWORD;2-STRING", 4, true }
	{ "user32.dll",   "MessageBeep", 0,   0,  "",                 1, false}


------[ 3.1.3 - Llamada a una API hookeada

Todas las llamadas a otros módulos se deben hacer a través de un puntero a
función global con el prototipo de la original para poder usarlo como la
original, después se deberán introducir en la tabla Extern que será
cambiada a la hora de poner los hooks. Se debe iniciar a la dirección de la
API original:

Código: Seleccionar todo


    typedef void (WINAPI * Sleep_t)(DWORD);
    
    Sleep_t hSleep = Sleep;
    
    void ** Extern[] =
    {
    	(void **) & hSleep
    };

En el one hook handler para llamar a la original se debe usar
hSleep( DWORD ) en vez de la original, para evitar stack buffers overflows.


------[ 3.1.4 - Hack de la biblioteca

Para crear hooks de tipo CALL (E8 Method) he creado una rutina parecida a
la rutina interna: detour_gen_jmp_immediate:

Código: Seleccionar todo


    inline PBYTE detour_ooh_gen_call_immediate
    (
        PBYTE pbCode, PBYTE pbCallVal
    )
    {
        PBYTE pbCallSrc = pbCode + 5;
        *pbCode++ = 0xE8;   // call +imm32
        *((INT32*&)pbCode)++ = (INT32)(pbCallVal - pbCallSrc);
        return pbCode;
    }

Ésta función será llamada cuando se ejecute el DetourOOHTransactionCommit.

Prototipo de DetourOOHTransactionCommit:

- LONG WINAPI DetourOOHTransactionCommit( void );

- Retorno: Igual al de la biblioteca original DetourTransactionCommit.

Para realizar los Attach he creado también un DetourOOHAttach, prototipo:

- LONG WINAPI DetourOOHAttach( PVOID, PVOID * );

- 1º param: Creador del handle_ooh que salta al one_hook_handler
C/C++.

- 2º param: Dirección dónde se debe introducir el hook de tipo CALL
(E8 Method)
.

- Retorno: Igual al de la biblioteca original DetourAttach.

Estas función son usadas en el POC en el DllMain para introducir los hooks.

Se deben usar las funciones de la biblioteca Detours original tal cual se
usan en DetourAttach y DetourTransactionCommit, es decir se deben llamar de
la siguiente manera:

Código: Seleccionar todo


    DetourTransactionBegin();
    DetourUpdateThread();
    DetourOOHAttach();
    DetourOOHAttach();
    ...
    DetourOOHTransactionCommit();


------[ 3.2 - Easy-hook

En los siguientes capitúlos se detallarán las funcionalidades citada en el
capítulo 3. Tambien se explicará un POC creado para usar el E8 Method con
la API unmanaged del easy-hook.


------[ 3.2.1- Capa en ensamblador

La capa de ensamblador es casi igual a la capa de Microsoft Detours (ver
capítulo 3.1.1), salvo tres diferencias:

1) El hook_caller ID no se obtiene metiendo un salto de tipo CALL, se
usa el parámetro InCallback de la API LhInstallHook, InCallback
se puede obtener con la API LhBarrierGetCallback desde el hook
handler
.

2) Las funciones de la capa en ensamblador han sido adaptadas para que
trabajen sin el valor de retorno que mete el salto de tipo CALL en
la pila.

3) Para mostrar información de debug, se crea una consola con la API
AllocConsole
debido a que la forma de crear un proceso para inyectar
una biblioteca RhCreateAndInject() del easy-hook no la crea por
defecto.


------[ 3.2.1.1- Creación handle_ooh

La capa de ensamblador es casi igual a la capa de Microsoft Detours (ver
capítulo 3.1.1.1), salvo la creación del hook_caller ID. Los hooks con la
API unmanaged de easy-hook se hacen usando LhInstallHook:

Código: Seleccionar todo


    EASYHOOK_NT_EXPORT LhInstallHook
    (
        void * InEntryPoint,
        void * InHookProc,
        void * InCallback,
        TRACED_HOOK_HANDLE OutHandle
    );

El parámetro InCallback para cada hook se puede obtener desde el hook
handler
usando la API LhBarrierGetCallback:

Código: Seleccionar todo


    EASYHOOK_NT_EXPORT LhBarrierGetCallback (PVOID** OutValue);

Así que el hook handler de bajo nivel solo tiene que llamar a esa API, para
obtener un hook_caller ID, igual que en la implementación Microsoft
Detours
, el hook_caller ID será la dirección de la API hookeada.

Para conseguir la dirección de la API hookeada, he usado el OutHandle
devuelto por LhInstallHook como InCallback. Mediante el OutHandle de tipo
TRACED_HOOK_HANDLE se puede obtener directamente el Entrypoint de la API
hookeada
en tiempo de ejecución:

Código: Seleccionar todo


    typedef struct _HOOK_TRACE_INFO_
    {
        PLOCAL_HOOK_INFO        Link;
    }HOOK_TRACE_INFO, *TRACED_HOOK_HANDLE;

El Link es tal que así:

Código: Seleccionar todo


    typedef struct _LOCAL_HOOK_INFO_
    {
        struct _LOCAL_HOOK_INFO_* Next;
        ULONG					  NativeSize;
    	UCHAR*					  TargetProc;
        ...
    } LOCAL_HOOK_INFO, *PLOCAL_HOOK_INFO;

Y el campo TargetProc se corresponde con el Entrypoint de la API hookeada.

Solo es necesario para acceder al mismo, poner el hook de la siguiente
manera:

Código: Seleccionar todo

    
    LhInstallHook
    (
    	API_TO_HOOK,
    	HOOK_HANDLER_ASSEMBLY,
    	& OUT_HOOK_HANDLE,
    	OUT_HOOK_HANDLE
    )

Y después desde el hook handler en ensamblador solo es necesario acceder al
OUT_HOOK_HANDLE creado por LhInstallHook:

Código: Seleccionar todo


        ... PUSHAD AND OTHER STUFF LIKE THE MICROSOFT DETOURS ...

        // Creation of hook_caller ID.
        PUSH EBX // Creación en la pila de un DWORD para InCallback.
		PUSH ESP // Dirección donde se introducirá InCallback.
		CALL DWORD PTR [LhBarrierGetCallback]  // Obtenemos InCallback.
		POP EBX // Obtenemos InCallback en EBX.
		MOV EBX, [EBX] // Accedemos al OUT_HOOK_HANDLE.
        MOV EBX, [EBX] // Accedemos al campi Link.
		ADD EBX,08h // Accedemos al campo Targetproc (Entrypoint de la API)
		MOV EBX, [EBX] // Obtenemos el valor de Targetproc

        ... OTHER STUFF LIKE THE MICROSOFT DETOURS
        ... PUSH EBX // Se introduce en la pila el hook_caller ID.
        ... OTHER STUFF LIKE THE MICROSOFT DETOURS

Para acceder al hook_caller ID y las capas de bajo nivel se hace igual
que desde la implementación de Microsoft Detours con GetHookCallerID().


------[ 3.2.2 - Llamada a una API hookeada - TDB

Con la implementación easy-hook se puede llamar directa o indirectamente
a una API hookeada, sin mayor preocupación que poner las ACLs de forma
correcta después de poner los hooks y en el hook handler.

Cuando se ponen los hooks no están habilitados, es necesario poner las ACLs
para activarlo a uno o varios hilos, para activar los hooks a todos los
hilos he usado la API SetExclusiveACL después de poner cada hook:

Código: Seleccionar todo


    EASYHOOK_NT_EXPORT LhSetExclusiveACL
    (
        ULONG * InThreadIdList,
        ULONG   InThreadCount,
        TRACED_HOOK_HANDLE InHandle
    );

El cual pone una ACL local para un hook de forma exclusiva, es decir que si
ponemos una ACL exclusiva al hilo que pone los hooks y ejecuta
RhWakeUpProcess para iniciar la ejecución del proceso, todos los hilos que
ejecuten una API hookeada, menos el que ha puesto la ACL, pasarán por el
hook handler.

Si se crea una ACL con el valor 0, será puesto el hilo que ha llamado a la
API como Exclusive:

Código: Seleccionar todo


    ULONG ACLEntries[1] = {0};

    // La siguiente llamada habilita un hook a todos los hilos menos al 
    // que la llama:
    LhSetExclusiveACL( ACLEntries, 1, OUT_HOOK_HANDLE );

    ...
    // Se empieza a ejecutar el proceso una vez puesto todos los hooks y 
       ACLs:
    RhWakeUpProcess();

El segundo parámetro es el número de ACLs, en éste caso 1.

EasyHook usa la innovación Thread-Deadlock-Barrier (TDB) para proteger
de la recursividad al llamar a una API hookeada, directa o
indirectamente. Las ACLs no son capaces de resolver la recursividad,
sólo debería usarse explicitamente para deshabilitar completamente los
hooks de un hilo.

En este caso se usará la API LhSetGlobalExclusiveACL, que desactivará todos
los hooks para el hilo indicado, en este caso el hilo que la llamar usando
el valor 0 en la ACL:

Código: Seleccionar todo


	ULONG                   ACLEntries[1] = {0};

	LhSetGlobalExclusiveACL( ACLEntries, 1 );

    ...

    LhSetGlobalInclusiveACL( ACLEntries, 1 );

    SafeRet( ... );

Y por último se habilitan todos los hooks para ese hilo y se retorna al
código original.


------[ 3.2.3 - one_hook_handler

Al igual que el one_hook_handler implementación Microsoft Detours (3.1.2),
el prototipo del hook handler global C/C++ debe ser:

- void one_hook_handler( handle_ooh_t * );

Se deberán usar las directivas citadas en el capítulo 3.

one_hook_handler es un POC de un API Spy muy beta y muy básico,
funcionalidades:

1) Dispone de una tabla en la que se debe indicar:

a) DLL donde está la API a hookear.

b) API a hookear.

c) ID: este campo debe ser 0, será iniciado cuando se introduzcan
los hooks y corresponderá con el hook_caller ID del
handle_ooh.

d) hook_handle: este campo debe ser 0, será iniciado cuando se
introduzcan los hooks y corresponderá con el
OUT_HANDLE de la API hookeada.

e) To log: Que parámetros se deben loggear y de que tipo son:

e.1) DWORD: Tipo de dato DWORD que será mostrado con un printf
%d
.

e.2) STRING: Dirección de memoria donde hay una string tipo C,
será mostrado con un printf %s.

f) Número de parámetros de la API (unidad int).

g) En caso de que sea necesario ejecutar la API original, con los
parámetros originales se debe indicar true, en caso contrario
false.

2) Se realizarán las acciones en el one hook handler indicadas en la
tabla y se mostrará un pequeño debug por la consola creada con
AllocConsole.

Tabla para introducir los hooks, se podría crear leyendo un fichero de
configuración y no directamente sobre el código:

Código: Seleccionar todo


    // dll            | func       | id | hh | to log         | par | orig?
	{ "kernel32.dll", "Sleep",       0,  0, "1-DWORD;",         1, true  },
	{ "user32.dll",   "MessageBoxA", 0,  0, "1-DWORD;2-STRING", 4, true  },
	{ "user32.dll",   "MessageBeep", 0,  0, "",                 1, false }


------[ 4.- Compilado, binarios & POC

- poc: creado para usarlo con el E8 Method del easy-hook y del
Detours.

- Detours Express 2.1- hacked: biblioteca Detours modificada para que
funcione con E8 Method.

- EasyHook 2.5 - unmaged stuff: E8 method sin modificar la biblioteca
original usando la API unmanaged.


------[ 4.1 - poc

poc.exe fue creado para usarlo con el E8 Method del easy-hook y del
Detours.


------[ 4.1.1 - Compilado

Solo es necesario compilar el poc.cpp, trae un .sln para abrir desde
el Microsoft Visual Studio.


------[ 4.1.2 - Uso

El .zip del paper trae ya el poc.exe necesario para probarlo:

C:\E8 Method\poc\bin>poc.exe

-- sleeping 5000... Starting... wait please
-- sleeping 5000 ... Done sleeping.

-- calling MessageBoxA...
-- Done calling MessageBoxA...

-- calling MessageBeep...
-- Done calling MessageBeep...


------[ 4.2 - Detours Express 2.1- hacked

Biblioteca Detours modificada para que funcione con E8 Method.


------[ 4.2.1 - Compilado

Para compilar el hack de la biblioteca es suficiente con copiar el
contenido de:
- E8 Method\Detours Express 2.1- hacked\src

En el directorio de src de la biblioteca Detours Express 2.1 original:
- Microsoft Research\Detours Express 2.1\src

Y para compilar el hook handler se debe copiar el contenido de:
- E8 Method\Detours Express 2.1- hacked\src\samples\hack

En el directorio:
- Microsoft Research\Detours Express 2.1\samples\hack

Para compilar la biblioteca solo es necesario compilarla como la original
con nmake en el directorio:
- Microsoft Research\Detours Express 2.1\src

Y para compilar la DLL con el hook handler solo es necesario o hacer el
nmake con la biblioteca hackeada o copiar el fichero detours.h hackeado
en:
- Microsoft Research\Detours Express 2.1\include

Y después compilar la DLL, el cual tiene un .sln para el Microsoft Visual
Studio.

Para inyectar la DLL se puede usar el withdll que viene con el Detours
Express 2.1
, para que nmake no genere un fichero manifest, he modificado
el Makefile original para que embeba el fichero en el ejecutable:

mt.exe -manifest $(BIND)\withdll.exe.manifest
-outputresource:$(BIND)\withdll.exe;1

Se debe copiar el Makefile situado en:
- E8 Method\Detours Express 2.1- hacked\src\samples\withdll\Makefile

En:
- Microsoft Research\Detours Express 2.1\samples\withdll

Y por último se debe hacer un nmake en los directorios (en este orden):

- Microsoft Research\Detours Express 2.1\samples\syelog
- Microsoft Research\Detours Express 2.1\samples\withdll

Esto es necesario por withdll tiene como dependencia el syelog.lib, pero
nosotros no usaremos dicha dependencia.

Y se crearán los ficheros en Microsoft Research\Detours Express 2.1\bin:

- hack.dll
- detoured.dll
- withdll.exe
- ... otros que no nos sirven.


------[ 4.2.2 - Uso

El .zip del paper trae ya los binarios necesarios para probarlo:

C:\E8 Method\Detours Express 2.1- hacked\bin>withdll.exe /d:hack.dll
"..\..\poc\bin\poc.exe"

withdll.exe: Starting: `..\..\poc\bin\poc.exe'
withdll.exe: with `C:\E8 Method\Detours Express 2.1-
hacked\bin\hack.dll'

withdll.exe: marked by `C:\E8 Method\Detours Express 2.1-
hacked\bin\detoured.dll'

hack.dll: Starting.
hack.dll: Detoured.

-- sleeping 5000... Starting... wait please
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging Sleep() (kernel32.dll) params:
Is necessary log param 1??
Yes, Logging DWORD: 5000
Execute original flag == true...
Calling with safe method to Sleep (kernel32.dll) with original
params...
-- sleeping 5000 ... Done sleeping.

-- calling MessageBoxA...
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging MessageBoxA() (user32.dll) params:
Is necessary log param 1??
Yes, Logging DWORD: 0
Is necessary log param 2??
Yes, Logging STRING: POC POC
Is necessary log param 3??
NO.
Is necessary log param 4??
NO.
Execute original flag == true...
Calling with safe method to MessageBoxA (user32.dll) with original
params...
-- Done calling MessageBoxA...

-- calling MessageBeep...
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging MessageBeep() (user32.dll) params:
Is necessary log param 1??
NO.
Execute original flag == false...
-- Done calling MessageBeep...
hack.dll: Removed


------[ 4.3 - EasyHook 2.5 - unmaged stuff

E8 method sin modificar la biblioteca original usando la API unmanaged.


------[ 4.3.1 - Compilado

Se debe copiar el sln:

- E8 Method\EasyHook 2.5 - unmaged stuff\src\EasyHook.sln

En:
- EasyHook 2.5 Beta Source Code\EasyHook.sln

Y después copiar el directorio:

- E8 Method\EasyHook 2.5 - unmaged stuff\src\Examples

En:
- EasyHook 2.5 Beta Source Code\Examples

Por último hacer un Build solution de la Easyhook DLL y los dos examples,
o simplemente un Build de todo.


------[ 4.3.2 - Uso

El .zip del paper trae ya los binarios necesarios para probarlo:

C:\E8 Method\EasyHook 2.5 - unmaged stuff\bin>unhack.exe
"..\..\poc\bin\poc.exe"

PID created: 2852

Y la salida en la consola creada con AllocConsole():

hook_caller_id: 0x7C802446
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging Sleep() (kernel32.dll) params:
Is necessary log param 1??
Yes, Logging DWORD: 5000
Execute original flag == true...
Calling with safe method to Sleep (kernel32.dll) with original params...
hook_caller_id: 0x7E3D07EA
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging MessageBoxA() (user32.dll) params:
Is necessary log param 1??
Yes, Logging DWORD: 0
Is necessary log param 2??
Yes, Logging STRING: POC POC
Is necessary log param 3??
NO.
Is necessary log param 4??
NO.
Execute original flag == true...
Calling with safe method to MessageBoxA (user32.dll) with original params..
.
hook_caller_id: 0x7E3B1F7B
Calling Sleep(0) from one_hook_handler without stack b0fs...
Logging MessageBeep() (user32.dll) params:
Is necessary log param 1??
NO.
Execute original flag == false...

MessageBeep detected, executing system("PAUSE")..

Press any key . . .
Imagen


Solo lo mejor es suficiente...
------[ 5.- TODO

Crear un proyecto propio (BSD/MIT) con las siguientes características:

0) Soporte oficial para x86 y x86_64 en Linux, Windows y FreeBSD.

1) Biblioteca para E8 Method y el método Detours convencional:

a.1) LDE para introducir los saltos y crear el trampolín.

a.2) Un sistema de ACLs como el del easy-hook para evitar
recursividad al llamar a APIs hookeada directa o
indirectamente.

a.3) Capa de bajo nivel para el soporte E8 Method:

a.3.1) Interfaces para obtener los parámetros, retorno seguro,
llamada segura, hook_caller ID...

a.3.2) Evidencias basadas en el microprocesador. Interfaces
para guardar y restaurar contextos.

a.3.3) Interfaces que soporte la obtención de parámetros y
reparación de pila según sea el tipo y subtipo del API
hookeada (Microsoft fastcall, Borland fastcall ...)
:

a.3.3.1) cdecl, stdcall, fastcall, syscall, thiscall...

Nota: debido a que se usa un solo hook handler, es
necesario conocer el tipo de convención de
llamada usa la API hookeada para poder devolver
el flujo original y obtener los parámetros de
forma correcta, ya vengan en la pila y/o
registros...

a.3.3.2) También se podrá especificar convenios propios
indicando como vienen los parámetros y en que
orden en la pila, registros... (en runtime).
Se implementará un mecanismo tipo Callback a
la hora de obtener los parámetros y restaurar
la pila, registros y también un tipo de
Callback para retornar al código original.

a.3.3.3) La interfaz de obtención de parámetros se
abstrae del tipo de convenio y solo obtendrá
datos en memoria y en el orden del convenio.

a.4) La implementación Detours convencional no será diferente a las
que existen.

a.5) Soporte para C/C++ y otros lenguajes de alto nivel para el
hook handler usando el E8 Method.

a.6) Sistema de IPC para envío de información desde el hook
handler
, para cada sistema soportado.

a.7) Y en un futuro quizá soporte para hooks en el kernel como el
Easyhook.

a.8) Y todas las típicas funcionalidades como inyección de código,
bibliotecas para las plataformas soportadas.

a.9) Interfaz para realizar/quitar los hooks y las típicas
funcionalidadesde éste tipo de bibliotecas desde los lenguajes
soportados.

a.10) ...


------[ 6.- Testing

+--------------------------+---------------+-------------------+
| SO (x86) | ooh easy-hook | ooh Detours |
+--------------------------+---------------+-------------------+
| Windows XP SP1/SP2/SP3 | SUPPORTED | SUPPORTED |
+--------------------------+---------------+-------------------+
| Windows Vista SP1 | SUPPORTED | SUPPORTED |
+--------------------------+---------------+-------------------+
| Windows 2008 R2 SP1/SP2 | SUPPORTED | SUPPORTED |
+--------------------------+---------------+-------------------+


------[ 7.- Ventajas y posibilidades

La ventaja principal es que no es necesario compilar código para añadir
nuevos hooks, solo se programa la funcionalidad principal y lo demás se
puede delegar a un fichero de configuración.

Algunas ideas:

1) API Spy basado en un fichero: para añadir APIs a loggear solo habrá
que actualizar un fichero de
configuración.

2) Analizador de malware: igual que en el API Spy...

3) Virtualizador de APIs: igual que en el APY Spy, además de añadir
como virtualizar qué búffer etc...

4) ... anything ....


------[ 8.- Conclusión

Implementar E8 Method puede ser una tarea dificil, hay que tener en cuenta
varios factores como tipo de convención de llamada para el hook handler y
la API hookeada, reparación de la pila, problemas típicos de los hooks...

Pero una vez bien implementado solo se necesita programar la funcionalidad
general. Es sobre todo útil el método cuando se crea una aplicación con un
mismo o parecido propósito para diferentes hooks, como un API Spy...

El paper incluye una versión POC del E8 Method y un hook handler, pronto
espero implementar la propuesta del TODO (ver capítulo 5).


------[ 9.- Agradecimientos

Traducción del abstract a Inglés: Patowc & Christian Martorella

Traducción del paper a Inglés: Patowc

easy-hook: Christoph Husse

friends: GriYo, Slay, tomac, alon, diwou, ilo & pluf


------[ 10.- Referencias

[R.1] .- Visual Studio, Microsoft Portable Executable and Common Object
File Format Specification:
- [Enlace externo eliminado para invitados]
PECOFF.mspx

[R.2] .- Iczelion's Win32 Assembly Homepage:
- [Enlace externo eliminado para invitados]

[R.3] .- MSDN LIBRARY:
- [Enlace externo eliminado para invitados]

[R.4] .- Hooks:
- [Enlace externo eliminado para invitados]

[R.5] .- Import address table hooks:
- [Enlace externo eliminado para invitados]

[R.6] .- Detours Library:
- [Enlace externo eliminado para invitados]

[R.7] .- LDE (Length-Disassembler Engine):
- [Enlace externo eliminado para invitados]

[R.8] .- EasyHook Library:
- [Enlace externo eliminado para invitados]

[R.9] .- Galen Hunt and Doug Brubacher - Detours: Binary Interception of
Win32 Functions:
- [Enlace externo eliminado para invitados]
HuntUsenixNt99.pdf

[R.10] .- Intel Architecture Software Developer's Manual, Volume 1:
Basic Architecture:
- [Enlace externo eliminado para invitados]
24319002.PDF

[R.11] .- Intel Architecture Software Developer's Manual, Volume 2:
Instruction Set Reference Manual:
- [Enlace externo eliminado para invitados]
24319102.PDF

[R.12] .- An Overview of Managed/Unmanaged Code Interoperability
- [Enlace externo eliminado para invitados]


------[ 11.- Código fuente y binarios

[Enlace externo eliminado para invitados]

Espero que les sea de ayuda...

Saludos !
Imagen


Solo lo mejor es suficiente...
Responder

Volver a “Otros lenguajes”