Una serie de programas cada vez más complejos que demuestran la función de enlace en Windows de 64 bits.
Escribí estos programas mientras me enseñaba cómo funciona el enlace de funciones. Pueden ser útiles para otras personas que intentan hacer lo mismo (o para mí en el futuro después de que vuelva a olvidar cómo funciona todo esto). Están destinados a ser examinados en orden. La primera vez que se utiliza una función en un programa, se incluirá en el archivo .cpp de ese programa de ejemplo específico, con su nombre precedido de un guión bajo. Cualquier ejemplo posterior que utilice la misma función utilizará la copia de esa función incluida en hooking_common.h, para minimizar la duplicación de código y mantener los programas de ejemplo posteriores lo suficientemente pequeños como para que aún sean fácilmente legibles.
He trabajado un poco para limpiarlos, pero los ejemplos posteriores todavía están un poco desordenados. Es importante tener en cuenta que el resultado final de este repositorio no es suficiente para escribir una biblioteca de enlaces con todas las funciones, pero sí es suficiente para comenzar por ese camino.
En tiempo de ejecución, algunos proyectos pueden depender de la construcción de otros proyectos (inyectar un dll requiere que se haya creado el dll) o de ejecutarse al mismo tiempo (para conectar un programa de destino es necesario que ya esté ejecutándose).
Todos los ejemplos se crearon con Visual Studio 2019 (v142) con Windows SDK 10.0.17763.0. No creo que haya nada aquí que dependa de la versión de VS o SDK, pero lo incluyo aquí por si acaso. Es casi seguro que hay algunas cosas que son específicas de MSVC.
Finalmente, el último ejemplo de trampolín instala un gancho en mspaint. Supongo que en algún momento en el futuro, una actualización de mspaint provocará que este ejemplo falle. En el momento de escribir este artículo, la versión actual de mspaint era 1909 (OS Build 18363.1016).
Los ejemplos se dividen en dos categorías: los que usan trampolines y los que no. Los ejemplos sin trampolín existen únicamente para demostrar la redirección del flujo del programa de una función a otra en diferentes situaciones. Construir trampolines es complicado, y cuando intentaba descubrir cómo funcionaba el enganche de funciones, fue de gran ayuda comenzar construyendo primero los ejemplos sin trampolines. Además, hay 4 "programas de destino" que se utilizan en ejemplos que quieren demostrar cómo instalar ganchos en diferentes procesos (ya en ejecución).
La mayoría de estos ejemplos pierden memoria relacionada con los ganchos. Realmente no me importa, porque estos ejemplos son solo para demostrar un concepto de enlace y porque de todos modos estas asignaciones "filtradas" deben existir hasta la finalización del programa.
Si bien no parece haber mucha terminología estándar para las técnicas de enlace de funciones, el código (y los archivos Léame) de este repositorio utilizan los siguientes términos:
Dado que estos ejemplos no crean trampolines al instalar sus ganchos, creo que estas funciones demuestran un enganche "destructivo", en el sentido de que la función original queda completamente inutilizable después de ser enganchada.
Un pequeño ejemplo de cómo sobrescribir los bytes iniciales de una función con una instrucción de salto que redirige el flujo del programa a una función diferente dentro del mismo programa. Dado que no se está construyendo ningún trampolín, esta operación es destructiva y la función original ya no se puede llamar. Este es el único ejemplo de 32 bits en el repositorio.
La versión de 64 bits del ejemplo anterior. En aplicaciones de 64 bits, las funciones pueden ubicarse lo suficientemente lejos en la memoria como para no ser accesibles mediante una instrucción de salto relativo de 32 bits. Dado que no existe una instrucción de salto relativa de 64 bits, este programa primero crea una función de "retransmisión", que contiene bytes para una instrucción jmp absoluta que puede llegar a cualquier lugar de la memoria (y salta a la función de carga útil). El salto de 32 bits que se instala en la función de destino salta a esta función de retransmisión, en lugar de inmediatamente a la carga útil.
Proporciona un ejemplo del uso de las técnicas del proyecto anterior para enlazar una función miembro, en lugar de una función libre.
Ligeramente diferente de los ejemplos anteriores, este programa muestra cómo instalar un gancho en una función miembro virtual obteniendo la dirección de esa función a través de la tabla virtual de un objeto. Ningún otro ejemplo trata sobre funciones virtuales, pero pensé que era lo suficientemente interesante como para incluirlo aquí.
El ejemplo más simple de instalación de un gancho en otro proceso en ejecución. Este ejemplo utiliza la biblioteca DbgHelp para ubicar una función en un proceso de destino (A - Destino con función libre) por nombre de cadena. Esto sólo es posible porque el programa de destino se crea con los símbolos de depuración habilitados. Si bien es simple, este ejemplo es un poco más largo que los programas anteriores debido a la gran cantidad de funciones nuevas que introduce (para localizar y manipular un proceso remoto).
Este ejemplo muestra cómo enlazar una función que otro proceso ha importado desde una DLL. Hay algunos matices sobre cómo obtener la dirección de una función dll en un proceso remoto debido a cómo funciona ASLR, que se demuestra aquí. Por lo demás, este ejemplo es casi idéntico al anterior.
Este ejemplo muestra cómo instalar un enlace en una función que no se importa mediante una DLL y que no está en la tabla de símbolos (probablemente porque el proceso remoto no tiene símbolos de depuración). Esto significa que no hay una manera (fácil) de encontrar la función de destino por el nombre de la cadena. En cambio, este ejemplo supone que ha utilizado un desensamblador como x64dbg para obtener la dirección virtual relativa (RVA) de la función que desea conectar. Este programa utiliza ese RVA para instalar un gancho.
Similar a lo anterior, excepto que este ejemplo utiliza la inyección de dll para instalar la función de carga útil en lugar de escribir bytes de código de máquina sin formato. Es mucho más fácil trabajar con esto, ya que sus cargas útiles se pueden escribir en C++ nuevamente. La carga útil de este ejemplo está contenida en el proyecto 08B-DLL-Payload.
Los siguientes ejemplos instalan trampolines al enganchar, lo que significa que el programa aún puede ejecutar la lógica en la función de destino después de que se haya instalado un gancho. Dado que la instalación de un gancho sobrescribe al menos los primeros 5 bytes de la función de destino, las instrucciones contenidas en estos 5 bytes se trasladan a la función de trampolín. Por lo tanto, llamar a la función de trampolín ejecuta efectivamente la lógica original de la función de destino.
El equivalente de instalación de trampolines del ejemplo n.° 2. Este ejemplo es un poco extraño porque quería demostrar cómo crear un trampolín sin necesidad de usar un motor de desmontaje. En este caso, la función de destino se creó para tener una instrucción conocida de 5 bytes al principio, por lo que podemos simplemente copiar los primeros cinco bytes de esa función a la función de trampolín. Esto significa que crear el trampolín es realmente fácil, ya que sabemos su tamaño exacto y que no utiliza ningún direccionamiento relativo que deba arreglarse. Si estuviera escribiendo un trampolín para un caso de uso realmente específico, probablemente podría salirse con la suya haciendo simplemente una variación de esto.
Este ejemplo muestra un escenario similar al anterior, excepto que esta vez estoy usando un desensamblador (capstone) para obtener los bytes que necesitamos robar de la función de destino. Esto permite que el código de enlace se use en cualquier función, no solo en aquellas que sabemos que serán casos fáciles. En realidad, están sucediendo muchas cosas en este ejemplo, porque se trata de saltar de un gancho dirigido (como el anterior) a crear una función de enlace genérica. El trampolín tiene que convertir llamadas/saltos relativos en instrucciones que utilizan direcciones absolutas, lo que complica aún más las cosas. Este tampoco es un ejemplo 100% pulido de enlace genérico, fallará con instrucciones de bucle y si intenta enlazar funciones con menos de 5 bytes de instrucciones.
Básicamente lo mismo que el anterior, excepto que este ejemplo incluye código para pausar todos los subprocesos en ejecución mientras instala un gancho. No se garantiza que esto sea seguro para subprocesos en todos los casos, pero definitivamente es mucho más seguro que no hacer nada.
Esto amplía el código de gancho/trampolín utilizado en los dos ejemplos anteriores para admitir la redirección de múltiples funciones a la misma carga útil y para permitir que las funciones de carga llamen a otras funciones con ganchos instalados en ellas.
Este es el primer ejemplo de trampolín que instala un gancho en un proceso diferente (en este caso, la aplicación de destino B - Target con funciones gratuitas desde DLL). Toda la lógica de enganche está contenida en una carga útil dll 13B: Carga útil DLL de función importada de Trampoline. No hay muchas novedades aquí, este ejemplo simplemente combina el enlace de trampolín ya hecho con las técnicas mostradas anteriormente para conectar una función importada desde un dll.
La joya de la corona del repositorio. Este ejemplo inyecta una carga útil dll (14B - Carga útil MSPaint de Trampoline Hook) en una instancia en ejecución de mspaint (debe iniciar mspaint usted mismo antes de ejecutar esto). El gancho instalado hace que los pinceles se dibujen en rojo, sin importar el color que haya seleccionado en MSPaint. Sinceramente, no hay nada aquí que no se haya mostrado en el ejemplo anterior, es genial ver esto funcionando en un programa no artificial.
Aplicación de destino simple que llama a una función gratuita en un bucle. Compilado con información de depuración incluida.
Aplicación de destino que llama a una función gratuita que ha sido importada desde un dll (B2 - GetNum-DLL) en un bucle.
Aplicación de destino que llama a una función miembro no virtual en un bucle.
Aplicación de destino que llama a una función miembro virtual en un bucle.