stringtranslate.com

Enganche

En programación de computadoras , el término enganche cubre una variedad de técnicas utilizadas para alterar o aumentar el comportamiento de un sistema operativo , de aplicaciones o de otros componentes de software mediante la interceptación de llamadas a funciones , mensajes o eventos pasados ​​entre componentes de software . El código que maneja tales llamadas a funciones, eventos o mensajes interceptados se llama gancho .

Los métodos de enlace son de particular importancia en el patrón de método de plantilla , donde el código común en una clase abstracta se puede aumentar con código personalizado en una subclase. En este caso, cada método de enlace se define en la clase abstracta con una implementación vacía que luego permite proporcionar una implementación diferente en cada subclase concreta.

El enganche se utiliza para muchos propósitos, incluida la depuración y la ampliación de la funcionalidad. Los ejemplos podrían incluir interceptar mensajes de eventos del teclado o del mouse antes de que lleguen a una aplicación, o interceptar llamadas del sistema operativo para monitorear el comportamiento o modificar la función de una aplicación u otro componente. También se usa ampliamente en programas de evaluación comparativa, por ejemplo, para medir la velocidad de fotogramas en juegos 3D, donde la salida y la entrada se realizan mediante enlace.

El enganche también puede ser utilizado por código malicioso. Por ejemplo, los rootkits , piezas de software que intentan hacerse invisibles falsificando el resultado de llamadas API que de otro modo revelarían su existencia, a menudo utilizan técnicas de enganche.

Métodos

Normalmente, los enlaces se insertan mientras el software ya se está ejecutando, pero el enlace es una táctica que también se puede emplear antes de que se inicie la aplicación. Ambas técnicas se describen con mayor detalle a continuación.

Modificación de fuente

El enganche se puede lograr modificando la fuente del ejecutable o biblioteca antes de que se ejecute una aplicación, mediante técnicas de ingeniería inversa . Esto generalmente se usa para interceptar llamadas a funciones para monitorearlas o reemplazarlas por completo.

Por ejemplo, utilizando un desensamblador , se puede encontrar el punto de entrada de una función dentro de un módulo . Luego se puede modificar para cargar dinámicamente algún otro módulo de biblioteca y luego hacer que ejecute los métodos deseados dentro de esa biblioteca cargada. Si corresponde, otro enfoque relacionado mediante el cual se puede lograr el enlace es alterando la tabla de importación de un ejecutable. Esta tabla se puede modificar para cargar cualquier módulo de biblioteca adicional, así como para cambiar el código externo que se invoca cuando la aplicación llama a una función.

Un método alternativo para lograr el enlace de funciones es interceptar llamadas a funciones a través de una biblioteca contenedora . Un contenedor es una versión de una biblioteca que carga una aplicación, con la misma funcionalidad de la biblioteca original que reemplazará. Es decir, todas las funciones a las que se puede acceder son esencialmente las mismas entre el original y el reemplazo. Esta biblioteca contenedora puede diseñarse para llamar a cualquiera de las funciones de la biblioteca original o reemplazarla con un conjunto de lógica completamente nuevo.

Modificación del tiempo de ejecución

Los sistemas operativos y el software pueden proporcionar los medios para insertar fácilmente enlaces de eventos en tiempo de ejecución . Está disponible siempre que el proceso que inserta el gancho tenga permiso suficiente para hacerlo. Microsoft Windows, por ejemplo, permite a los usuarios insertar ganchos que pueden usarse para procesar o modificar eventos del sistema y eventos de aplicaciones para cuadros de diálogo , barras de desplazamiento y menús , así como otros elementos. También permite un gancho para insertar, eliminar, procesar o modificar eventos de teclado y mouse . Linux proporciona otro ejemplo en el que los ganchos se pueden usar de manera similar para procesar eventos de red dentro del kernel a través de NetFilter .

Cuando no se proporciona dicha funcionalidad, una forma especial de enlace emplea la interceptación de las llamadas a funciones de biblioteca realizadas por un proceso. El enlace de función se implementa cambiando las primeras instrucciones de código de la función de destino para saltar a un código inyectado. Alternativamente, en sistemas que utilizan el concepto de biblioteca compartida , la tabla de vectores de interrupción o la tabla de descriptores de importación se pueden modificar en la memoria. Esencialmente, estas tácticas emplean las mismas ideas que las de modificación de fuentes, pero en lugar de eso alteran instrucciones y estructuras ubicadas en la memoria de un proceso una vez que ya se está ejecutando.

Código de muestra

Enganche de tabla de métodos virtuales

Siempre que una clase define/hereda una función (o método) virtual, los compiladores agregan una variable miembro oculta a la clase que apunta a una tabla de métodos virtuales (VMT o Vtable). La mayoría de los compiladores colocan el puntero VMT oculto en los primeros 4 bytes de cada instancia de la clase. Un VMT es básicamente una matriz de punteros a todas las funciones virtuales que pueden llamar las instancias de la clase. En tiempo de ejecución, estos punteros se configuran para apuntar a las funciones correctas, porque en tiempo de compilación , aún no se sabe si se llamará a la función base o si se llamará a una versión anulada de la función desde una clase derivada (permitiendo así para polimorfismo ). Por lo tanto, las funciones virtuales se pueden conectar reemplazando los punteros a ellas dentro de cualquier VMT en el que aparezcan. El siguiente código muestra un ejemplo de un enlace VMT típico en Microsoft Windows, escrito en C++. [1]

#include <iostream> #incluye "windows.h" usando el espacio de nombres std ; clase VirtualClass { público : int número ; virtual void VirtualFn1 () //Esta es la función virtual que se enganchará. { cout << "VirtualFn1 llamado " << número ++ << " \n\n " ; } }; usando VirtualFn1_t = void ( __thiscall * )( void * thisptr ); VirtualFn1_t orig_VirtualFn1 ;                                   void __fastcall hkVirtualFn1 ( void * thisptr , int edx ) //Esta es nuestra función de enlace que haremos que el programa llame en lugar de la función VirtualFn1 original después de realizar el enlace. { cout << "Función de enlace llamada" << " \n " ; orig_VirtualFn1 ( thisptr ); //Llama a la función original. } int main () { Clase Virtual * miClase = nueva Clase Virtual (); //Crea un puntero a una instancia asignada dinámicamente de VirtualClass. vacío ** vTablePtr = * reinterpret_cast < vacío ***> ( miClase ); //Encuentre la dirección que apunta a la base del VMT de VirtualClass (que luego apunta a VirtualFn1) y guárdela en vTablePtr. DWORD protección antigua ; VirtualProtect ( vTablePtr , 4 , PAGE_EXECUTE_READWRITE y oldProtection ) ; // Elimina la protección de la página al inicio de VMT para que podamos sobrescribir su primer puntero. orig_VirtualFn1 = reinterpret_cast <VirtualFn1_t> ( * vTablePtr ) ; _ //Almacena el puntero a VirtualFn1 desde el VMT en una variable global para que se pueda acceder a él nuevamente más tarde después de que su entrada en el VMT haya sido //sobrescrita con nuestra función de enlace. * vTablePtr = & hkVirtualFn1 ; //Sobrescribe el puntero a VirtualFn1 dentro de la tabla virtual con un puntero a nuestra función de enlace (hkVirtualFn1). VirtualProtect ( vTablePtr , 4 , oldProtection , 0 ); //Restaurar la protección de la página anterior. miClase -> VirtualFn1 (); //Llama a la función virtual desde nuestra instancia de clase. Debido a que ahora está enlazado, esto en realidad llamará a nuestra función de enlace (hkVirtualFn1). miClase -> VirtualFn1 (); miClase -> VirtualFn1 (); eliminar miClase ; devolver 0 ; }                                                                   

Es importante tener en cuenta que todas las funciones virtuales deben ser funciones miembro de la clase, y todas las funciones miembro de la clase (no estáticas) se llaman con la convención de llamada __thiscall (a menos que la función miembro tome un número variable de argumentos, en cuyo caso se llama con __cdecl). La convención de llamada __thiscall pasa un puntero a la instancia de la clase que llama (comúnmente denominada puntero "this") a través del registro ECX (en la arquitectura x86). Por lo tanto, para que una función de enlace intercepte correctamente el puntero "este" que se pasa y lo tome como argumento, debe buscar en el registro ECX. En el ejemplo anterior, esto se hace configurando la función de enlace (hkVirtualFn1) para usar la convención de llamada __fastcall, lo que hace que la función de enlace busque en el registro ECX uno de sus argumentos.

Tenga en cuenta también que, en el ejemplo anterior, la función de enlace (hkVirtualFn1) no es una función miembro en sí misma, por lo que no puede utilizar la convención de llamada __thiscall. En su lugar, se debe usar __fastcall porque es la única otra convención de llamada que busca un argumento en el registro ECX.

Gancho de evento de teclado C#

El siguiente ejemplo se conectará a eventos de teclado en Microsoft Windows utilizando Microsoft .NET Framework .

usando System.Runtime.InteropServices ; Ganchos de espacio de nombres ; public class KeyHook { /* Variables miembro */ protected static int Hook ; Delegado de teclado de bajo nivel estático protegido Delegado ; objeto de solo lectura estático protegido Lock = nuevo objeto (); bool estático protegido IsRegistered = false ;                          /* Importaciones de DLL */ [DllImport("user32")] privado estático externo int SetWindowsHookEx ( int idHook , LowLevelKeyboardDelegate lpfn , int hmod , int dwThreadId );              [DllImport("user32")] externo estático privado int CallNextHookEx ( int hHook , int nCode , int wParam , KBDLLHOOKSTRUCT lParam );             [DllImport("user32")] externo estático privado int UnhookWindowsHookEx ( int hHook );       /* Tipos y constantes */ delegado protegido int LowLevelKeyboardDelegate ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ); constante privada int HC_ACTION = 0 ; constante privada int WM_KEYDOWN = 0 x0100 ; constante privada int WM_KEYUP = 0 x0101 ; constante privada int WH_KEYBOARD_LL = 13 ;                                   [StructLayout(LayoutKind.Sequential)] estructura pública KBDLLHOOKSTRUCT { public int vkCode ; código de escaneo int público ; banderas internas públicas ; tiempo int público ; público int dwExtraInfo ; }                     /* Métodos */ static private int LowLevelKeyboardHandler ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ) { if ( nCode == HC_ACTION ) { if ( wParam == WM_KEYDOWN ) System . Consola . Afuera . WriteLine ( "Tecla presionada: " + lParam . vkCode ); de lo contrario si ( wParam == WM_KEYUP ) Sistema . Consola . Afuera . WriteLine ( "Tecla arriba: " + lParam . vkCode ); } devolver CallNextHookEx ( Hook , nCode , wParam , lParam ); }                                       public static bool RegisterHook () { lock ( Lock ) { if ( IsRegistered ) devuelve verdadero ; Delegado = LowLevelKeyboardHandler ; Hook = SetWindowsHookEx ( WH_KEYBOARD_LL , Delegado , Mariscal . GetHINSTANCE ( Sistema . Reflexión . Asamblea . GetExecutingAssembly (). GetModules ()[ 0 ] ). ToInt32 (), 0 );                         si Gancho ! = 0 ) devuelve IsRegistered = verdadero ; Delegado = nulo ; falso retorno ; } }               bool estático público UnregisterHook () { bloquear ( Bloquear ) { return IsRegistered = ( UnhookWindowsHookEx ( Hook ) ! = 0 ); } } }               

API/enganche de función/intercepción mediante instrucción JMP, también conocida como empalme

El siguiente código fuente es un ejemplo de un método de enlace API/función que enlaza sobrescribiendo los primeros seis bytes de una función de destino con una instrucción JMP a una nueva función. El código se compila en un archivo DLL y luego se carga en el proceso de destino utilizando cualquier método de inyección de DLL . Utilizando una copia de seguridad de la función original, se podrían restaurar los primeros seis bytes nuevamente para que la llamada no se interrumpa. En este ejemplo, la función de la API win32 MessageBoxW está enganchada. [2]

/* Esta idea se basa en el enfoque chrom-lib, distribuido bajo licencia GNU LGPL. Fuente chrom-lib: https://github.com/linuxexp/chrom-lib Copyright (C) 2011 Raja Jamwal */ #include <windows.h> #define TAMAÑO 6   typedef int ( WINAPI * pMessageBoxW ) ( HWND , LPCWSTR , LPCWSTR , UINT ); // Prototipo de cuadro de mensaje int WINAPI MyMessageBoxW ( HWND , LPCWSTR , LPCWSTR , UINT ); // Nuestro desvío               anular BeginRedirect ( LPVOID ); pMessageBoxW pOrigMBAddress = NULL ; // dirección del BYTE original oldBytes [ TAMAÑO ] = { 0 }; // copia de seguridad BYTE JMP [ TAMAÑO ] = { 0 }; // instrucción JMP de 6 bytes DWORD oldProtect , myProtect = PAGE_EXECUTE_READWRITE ;                       INT APIENTRY DllMain ( HMODULE hDLL , DWORD Motivo , LPVOID reservado ) { switch ( Motivo ) { case DLL_PROCESS_ATTACH : // si está adjunto pOrigMBAddress = ( pMessageBoxW ) GetProcAddress ( GetModuleHandleA ( "user32.dll" ), // obtener la dirección del "MessageBoxW original " ); if ( pOrigMBAddress ! = NULL ) BeginRedirect ( MyMessageBoxW ); // comienza a desviarse del descanso ;                                   caso DLL_PROCESS_DETACH : VirtualProtect (( LPVOID ) pOrigMBAddress , TAMAÑO , myProtect y oldProtect ) ; // asigna protección de lectura y escritura memcpy ( pOrigMBAddress , oldBytes , TAMAÑO ); // restaurar copia de seguridad VirtualProtect (( LPVOID ) pOrigMBAddress , TAMAÑO , oldProtect y myProtect ) ; // restablecer la protección                 caso DLL_THREAD_ATTACH : caso DLL_THREAD_DETACH : ruptura ; } devuelve VERDADERO ; }              void BeginRedirect ( LPVOID nuevaFunción ) { BYTE tempJMP [ TAMAÑO ] = { 0xE9 , 0x90 , 0x90 , 0x90 , 0x90 , 0xC3 }; // 0xE9 = JMP 0x90 = NOP 0xC3 = RET memcpy ( JMP , tempJMP , TAMAÑO ); // almacena la instrucción jmp en JMP DWORD JMPSize = (( DWORD ) newFunction - ( DWORD ) pOrigMBAddress - 5 ); // calcular la distancia de salto VirtualProtect (( LPVOID ) pOrigMBAddress , TAMAÑO , // asignar protección de lectura y escritura PAGE_EXECUTE_READWRITE , & oldProtect ); memcpy ( oldBytes , pOrigMBAddress , TAMAÑO ); // hacer copia de seguridad memcpy ( & JMP [ 1 ], & JMPSize , 4 ); // llena los nop con la distancia de salto (JMP,distancia(4bytes),RET) memcpy ( pOrigMBAddress , JMP , TAMAÑO ); // establece la instrucción de salto al comienzo de la función original VirtualProtect (( LPVOID ) pOrigMBAddress , TAMAÑO , oldProtect y myProtect ) ; // restablecer la protección }                                                     int WINAPI MyMessageBoxW ( HWND hWnd , LPCWSTR lpText , LPCWSTR lpCaption , UINT uiType ) { VirtualProtect ( ( LPVOID ) pOrigMBAddress , TAMAÑO , myProtect y oldProtect ); // asigna protección de lectura y escritura memcpy ( pOrigMBAddress , oldBytes , TAMAÑO ); // restaurar copia de seguridad int retValue = MessageBoxW ( hWnd , lpText , lpCaption , uiType ); // obtiene el valor de retorno de la función original memcpy ( pOrigMBAddress , JMP , TAMAÑO ); // configurar la instrucción de salto nuevamente VirtualProtect (( LPVOID ) pOrigMBAddress , TAMAÑO , oldProtect , & myProtect ); // restablecer la protección return retValue ; // devuelve el valor de retorno original }                                          

Gancho de filtro de red

Este ejemplo muestra cómo utilizar el enlace para alterar el tráfico de red en el kernel de Linux usando Netfilter .

#incluye <linux/module.h> #incluye <linux /kernel.h> #incluye <linux/skbuff.h>   #incluir <linux/ip.h> #incluir <linux/tcp.h> #incluir <linux/in.h> #incluir <linux/netfilter.h> #incluir <linux/netfilter_ipv4.h>     /* Puerto en el que queremos descartar paquetes */ static const uint16_t port = 25 ;     /* Esta es la función de enlace en sí */ static unsigned int hook_func ( unsigned int hooknum , struct sk_buff ** pskb , const struct net_device * in , const struct net_device * out , int ( * okfn )( struct sk_buff * )) { struct iphdr * iph = ip_hdr ( * pskb ); estructura tcphdr * tcph , tcpbuf ;                              si ( iph -> protocolo ! = IPPROTO_TCP ) devuelve NF_ACCEPT ;      tcph = skb_header_pointer ( * pskb , ip_hdrlen ( * pskb ), tamaño de ( * tcph ), & tcpbuf ); si ( tcph == NULL ) devuelve NF_ACCEPT ;            retorno ( tcph -> destino == puerto ) ? NF_DROP : NF_ACCEPT ; }       /* Se utiliza para registrar nuestra función de enlace */ static struct nf_hook_ops nfho = { . gancho = función_gancho , . número de gancho = NF_IP_PRE_ROUTING ,. pf = NFPROTO_IPV4 ,. _ prioridad = NF_IP_PRI_FIRST , };                 estático __init int my_init ( void ) { return nf_register_hook ( & nfho ); }     estático __exit void my_exit ( void ) { nf_unregister_hook ( & nfho ); }    módulo_init ( mi_init ); salida_módulo ( mi_salida );

Enganche IAT interno

El siguiente código demuestra cómo enlazar funciones que se importan desde otro módulo. Esto se puede utilizar para enlazar funciones en un proceso diferente al proceso de llamada. Para esto, el código debe compilarse en un archivo DLL y luego cargarse en el proceso de destino utilizando cualquier método de inyección de DLL . La ventaja de este método es que es menos detectable por el software antivirus y/o el software anti-trampas ; se podría convertir esto en un gancho externo que no utilice llamadas maliciosas. El encabezado del ejecutable portátil contiene la tabla de direcciones de importación (IAT), que se puede manipular como se muestra en la fuente siguiente. La siguiente fuente se ejecuta en Microsoft Windows.


#incluir <ventanas.h> typedef int ( __stdcall * pMessageBoxA ) ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ); //Este es el 'tipo' de la llamada MessageBoxA. pMessageBoxA RealMessageBoxA ; //Esto almacenará un puntero a la función original.             void DetourIATptr ( const char * función , void * nueva función , módulo HMODULE );       int __stdcall NewMessageBoxA ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ) { //Nuestra función falsa printf ( "La cadena enviada a MessageBoxA fue: %s \n " , lpText ); devolver RealMessageBoxA ( hWnd , lpText , lpCaption , uType ); //Llamar a la función real }                   int main ( int argc , CHAR * argv []) { DetourIATptr ( "MessageBoxA" , ( void * ) NewMessageBoxA , 0 ); //Engancha la función MessageBoxA ( NULL , "Solo un cuadro de mensaje" , "Solo un cuadro de mensaje" , 0 ); //Llama a la función: esto invocará nuestro gancho falso. devolver 0 ; }              void ** IATfind ( const char * function , HMODULE module ) { // Busque la entrada IAT (Tabla de direcciones de importación) específica para la función dada. int ip = 0 ; si ( módulo == 0 ) módulo = GetModuleHandle ( 0 ); PIMAGE_DOS_HEADER pImgDosHeaders = ( PIMAGE_DOS_HEADER ) módulo ; PIMAGE_NT_HEADERS pImgNTHeaders = ( PIMAGE_NT_HEADERS )(( LPBYTE ) pImgDosHeaders + pImgDosHeaders -> e_lfanew ); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = ( PIMAGE_IMPORT_DESCRIPTOR )(( LPBYTE ) pImgDosHeaders + pImgNTHeaders -> OpcionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_IMPORT ]. VirtualAddress );                            if ( pImgDosHeaders -> e_magic != IMAGE_DOS_SIGNATURE ) printf ( "Error de libPE: e_magic no es una firma de DOS válida \n " );   for ( IMAGE_IMPORT_DESCRIPTOR * iid = pImgImportDesc ; iid -> Nombre ! = NULL ; iid ++ ) { for ( int funcIdx = 0 ; * ( funcIdx + ( LPVOID * ))( iid -> FirstThunk + ( TALLA_T ) módulo )) != NULL ; funcIdx ++ ) { char * modFuncName = ( char * )( * ( funcIdx + ( TAMAÑO_T * )( iid -> OriginalFirstThunk + ( TAMAÑO_T ) módulo )) + ( TAMAÑO_T ) módulo + 2 ); const uintptr_t nModFuncName = ( uintptr_t ) modFuncName ; bool esCadena = ! ( nModFuncName & ( tamaño de ( nModFuncName ) == 4 ? 0x80000000 : 0x8000000000000000 )); if ( isString ) { if ( ! _stricmp ( función , modFuncName )) return funcIdx + ( LPVOID * ) ( iid -> FirstThunk + ( TALLA_T ) módulo ); } } } devuelve 0 ; }                                                          void DetourIATptr ( const char * función , void * nueva función , módulo HMODULE ) { void ** funcptr = IATfind ( función , módulo ); if ( * funcptr == nuevafunción ) retorno ;                DWORD derechos antiguos , derechos nuevos = PAGE_READWRITE ; //Actualiza la protección a READWRITE VirtualProtect ( funcptr , sizeof ( LPVOID ) , newrights y oldrights );       RealMessageBoxA = ( pMessageBoxA ) * funcptr ; //Algunos compiladores requieren la conversión (como "MinGW"), aunque no estoy seguro acerca de MSVC * funcptr = newfunction ;     //Restaurar los antiguos indicadores de protección de memoria. VirtualProtect ( funcptr , tamaño de ( LPVOID ), derechos antiguos y derechos nuevos ) ; }   

Ver también

Referencias

  1. ^ psyfl, [1]
  2. ^ Para obtener más información, consulte http://ntvalk.blogspot.nl/2013/11/hooking-explained-detouring-library.html

enlaces externos

ventanas

linux

Emacs

OS X e iOS

Enganche API en profundidad