Red de conocimiento informático - Conocimiento informático - Captura de palabras en pantalla

Captura de palabras en pantalla

La tecnología de "selección de palabras en la pantalla del mouse" se usa ampliamente en diccionarios electrónicos, como software como Sitonglifang y Kingsoft PowerWord. Esta tecnología parece simple, pero de hecho, es difícil de implementar en el sistema WINDOWS. Es muy complicado en términos generales, hay dos formas de implementarlo:

La primera: implementarlo interceptando algunas llamadas API a GDI, como TextOut, TextOutA, etc.

El segundo método: hacer una copia de cada contexto de dispositivo (DC) y realizar un seguimiento de todas las operaciones que modifican el contexto (DC).

El segundo método es más potente, pero tiene poca compatibilidad, mientras que el primer método utiliza la interceptación de llamadas a la API de Windows. El poder de esta tecnología puede superar con creces su imaginación. No es exagerado. La tecnología de interceptación de API de Windows puede transformar todo el sistema operativo. De hecho, ¡así es como se implementan muchas plataformas chinas de Windows complementarias! Y esta tecnología es el tema de este artículo.

Específicamente, la interceptación de llamadas a la API de Windows se puede dividir en dos métodos:

El primer método reescribe directamente la imagen de WinAPI en la memoria e incrusta el código ensamblador, de modo que cuando se llama , salta a la dirección especificada y se ejecuta para interceptar; el segundo método es reescribir la IAT (tabla de direcciones de entrada de la tabla de direcciones de importación) y redirigir la llamada de la función WinAPI para interceptar WinAPI.

La implementación del primer método es más engorrosa y difícil en Win95 y 98. Esto se debe a que, aunque Microsoft dice que la API WIN16 solo se conserva por compatibilidad, los programadores deben llamarla tanto como sea posible como The 32. ¡La API de bits en realidad no es así en absoluto! La mayoría de las API de 32 bits dentro de WIN 9X se han convertido para llamar a las API de 16 bits del mismo nombre, lo que significa que debemos incrustar código ensamblador de 16 bits en las funciones interceptadas.

Lo que vamos a presentar es el segundo método de interceptación, que es relativamente estable y tiene buena compatibilidad con Win95, 98 y NT. Dado que necesitamos utilizar conocimientos de nivel inferior sobre la administración de la memoria virtual de Windows, romper los límites del proceso, inyectar código en el espacio de proceso de la aplicación, el formato de archivo PE (Portable Executable) y la IAT (Tabla de direcciones de entrada), primero El conocimiento involucrado será Se presentará brevemente y, finalmente, se proporcionará el código clave para la parte de interceptación.

Hablemos primero de la gestión de la memoria virtual de Windows. Windows9X asigna 4 GB de espacio de direcciones a cada proceso. Para NT, este número es de 2 GB. El sistema reserva el espacio de direcciones entre 2 GB y 4 GB para prohibir el acceso al proceso. En Win9X, esta parte del espacio de direcciones virtuales es de 2 GB a 4 GB. , es compartido por todos los procesos WIN32. Esta parte del espacio de direcciones se carga con DLL Win32 compartida, archivos asignados en memoria y VXD, administrador de memoria y código del sistema de archivos. En Win9X, esta parte es para cada proceso. Por eso el sistema operativo Win9X no es lo suficientemente robusto.

Win9X reserva un espacio de direcciones de 0 a 4 MB para el sistema operativo de 16 bits, y el espacio de direcciones entre 4 MB y 2 GB es el espacio de direcciones privado del proceso Win32, porque el espacio de direcciones de cada proceso es relativamente independiente, en otras palabras, si el programa quiere interceptar llamadas API en otros procesos, debe romper el muro límite del proceso e inyectar código para interceptar llamadas API en otros procesos. Dejamos esta tarea para que la complete la función de enlace (SetWindowsHookEx). Con respecto a cómo crear una biblioteca de enlaces dinámicos que contenga enlaces del sistema, "Computer Expert Magazine" ya ha tenido una introducción especial, por lo que no entraré en detalles aquí.

Todas las funciones de enlace del sistema deben estar en la biblioteca dinámica. En este caso, cuando el proceso llama implícita o explícitamente a una función en la biblioteca dinámica, el sistema asignará la biblioteca dinámica a la biblioteca virtual del proceso. En el espacio de direcciones, esto hace que la DLL se convierta en parte del proceso, se ejecute como este proceso y use la pila de este proceso, es decir, la función de enlace inyecta el código en la biblioteca de enlaces dinámicos. espacio de otros procesos GUI (procesos que no son GUI, la función de enlace no tiene poder). Cuando la DLL que contiene el enlace se inyecta en otros procesos, la dirección base de cada módulo (EXE y DLL) se puede asignar a la memoria virtual de este proceso. obtenerse, como por ejemplo:

HMODULE hmodule =GetModuleHandle("Mypro.exe");

En un programa MFC, podemos usar la función AfxGetInstanceHandle() para obtener la dirección base del módulo. El lugar donde EXE y DLL se asignan al espacio de memoria virtual está determinado por su dirección base. Sus direcciones base las determina el vinculador en el momento del enlace. Cuando crea un nuevo proyecto Win32, el vinculador VC++ utiliza la dirección base predeterminada 0x00400000. La dirección base de un módulo se puede cambiar a través de la opción BASE del vinculador. EXE generalmente se asigna a la memoria virtual en 0x00400000, y las DLL también tienen diferentes direcciones base y generalmente se asignan al mismo espacio de direcciones virtuales en diferentes procesos.

El sistema asigna EXE y DLL intactos al espacio de la memoria virtual y su estructura en la memoria es la misma que la estructura de archivos estáticos en el disco. Es decir, formato de archivo PE (Portable Executable). Después de obtener la dirección base del módulo de proceso, podemos enumerar exhaustivamente la matriz IMAGE_IMPORT_DESCRIPTOR de este módulo de acuerdo con el formato del archivo PE para ver si la biblioteca de enlaces dinámicos donde necesitamos interceptar se introduce en el espacio del proceso. por ejemplo, necesitamos interceptar "TextOutA", debe verificar si se ha introducido "Gdi32.dll".

Hablando de eso, necesitamos introducir el formato del archivo PE, como se muestra a la derecha. Este es un diagrama de bloques aproximado del formato del archivo PE. El frente es el encabezado del archivo. No es necesario prestarle atención. Desde detrás del encabezado opcional del archivo PE. El comienzo es la descripción de cada segmento en el archivo, lo que indica que siguen los datos reales del segmento. De hecho, solo nos importa un segmento, que es el ". "idata". Este segmento contiene toda la información de la función importada y la dirección RVA (dirección virtual relativa) de la IAT (tabla de direcciones de importación).

En este punto, todo el principio de interceptar la API de Windows quedará claro. De hecho, todas las llamadas de proceso a una función API determinada siempre se transfieren a través de un lugar en el archivo PE. Esta es la tabla de direcciones de entrada IAT (Importar) en la sección ".idata" del módulo (puede ser un EXE o DLL). . Tabla de direcciones). Están los nombres de funciones y las direcciones de todas las demás DLL llamadas por este módulo. Las llamadas a funciones a otras DLL en realidad simplemente saltan a la tabla de direcciones de entrada y luego saltan a la entrada de función real de la DLL desde la tabla de direcciones de entrada.

Específicamente, accederemos a la información de la DLL importada en la sección ".idata" a través de la matriz IMAGE_IMPORT_DESCRIPTOR, y luego accederemos a cada DLL importada para una DLL importada a través de la matriz IMAGE_THUNK_DATA Información de la función, busque la. salte la dirección de la función que necesitamos interceptar y luego cámbiela a la dirección de nuestra propia función... El método específico se explicará en detalle en el código clave a continuación.

Después de haber hablado de tantos principios, volvamos ahora al tema de la "adquisición de palabras en la pantalla del ratón". Además de interceptar las funciones API, es necesario realizar otros trabajos para implementar la "selección de palabras en la pantalla del mouse". En pocas palabras, un proceso completo de selección de palabras se puede resumir en los siguientes pasos:

1. Instale un gancho de mouse y obtenga mensajes del mouse a través de la función de gancho.

Función API utilizada: SetWindowsHookEx

2. Obtenga la posición actual del mouse, envíe un mensaje de redibujo a la ventana debajo del mouse y permita que llame a la función del sistema para volver a dibujar la ventana.

Funciones API utilizadas: WindowFromPoint, ScreenToClient, InvalidateRect

3. Intercepta la llamada a la función del sistema y obtiene los parámetros, que es la palabra que queremos obtener.

Para la mayoría de las aplicaciones de Windows, si queremos recuperar palabras, necesitamos interceptar la función "TextOutA" en "Gdi32.dll".

Primero imitemos la función TextOutA y escribamos nuestra propia función MyTextOutA, como por ejemplo:

BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString, int cbString)

{

// La salida de lpszString se procesa aquí

// Luego se llama a la función TextOutA genuina

}

Coloque esta función en la biblioteca de enlaces dinámicos con el gancho instalado y luego llame a la función HookImportFunction que proporcionamos en último lugar para interceptar la llamada del proceso a la función TextOutA, saltar a nuestra función MyTextOutA y completar la captura de la cadena de salida. .

Uso de HookImportFunction:

HOOKFUNCDESC hd;

PROC pOrigFuns;

hd.szFunc="TextOutA";

hd.pProc=(PROC)MyTextOutA;

HookImportFunction (AfxGetInstanceHandle(),"gdi32.dll",&hd,pOrigFuns);

La fuente de HookImportFunction se proporciona a continuación Código, creo que los comentarios detallados no le dificultarán comprender cómo se implementa la interceptación. Ok, vamos:

////////////////. ////////////////////////////// Comenzar /////////////////// // ///////////////////////////////////////////////

#include

//Esto define una macro que genera punteros

#define MakePtr(cast, ptr, AddValue) (cast)((DWORD )(ptr )+(DWORD)(AddValue))

// La estructura HOOKFUNCDESC está definida, usamos esta estructura como parámetro para pasar a la función HookImportFunction

typedef struct tag_HOOKFUNCDESC

{

LPCSTR szFunc; // El nombre de la función a enganchar.

PROC pProc; // El procedimiento a ejecutar.

} HOOKFUNCDESC , * LPHOOKFUNCDESC;

// Esta función monitorea si el sistema actual es WindowNT

BOOL IsNT();

// Esta función obtiene hModule, es decir, necesitamos interceptar el descriptor de importación del módulo DLL donde se encuentra la función

PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);

// Nuestra función principal

BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule,

LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)

{

/////////// ///////////// El siguiente código comprueba la validez de los parámetros ///////////////// /////////////

_ASSERT(szImportModule);

_ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));

#ifdef _DEBUG

if ( paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));

_ASSERT(paHookFunc.szFunc);

_ASSERT(*paHookFunc.s

zFunc != '\0');

_ASSERT(!IsBadCodePtr(paHookFunc.pProc));

#endif

if ((szImportModule == NULL ) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))

{

_ASSERT(FALSE);

SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

devuelve FALSO;

}

////////////////////////// / ///////////////////////////////////////////////// ///// //

// Supervisar si el módulo actual está por encima del espacio de memoria virtual de 2 GB

// Esta parte de la memoria de direcciones es compartida por el proceso Win32

if (!IsNT() && ((DWORD)hModule >= 0x80000000))

{

_ASSERT(FALSE);

SetLastErrorEx(ERROR_INVALID_HANDLE , SLE_ERROR);

return FALSE;

}

// Borrar

if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC));

// Llame a la función GetNamedImportDescriptor() para obtener hModule; es decir, necesitamos

// el descriptor de importación del módulo DLL donde se ubica la función interceptada (descriptor de importación)

PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);

if (pImportDesc == NULL)

return FALSE; // Si está vacío, entonces el módulo no ha sido introducido por el proceso actual

// Obtenga la información THUNK original del módulo DLL, porque la información original en la matriz pImportDesc->FirstThunk ha sido

// En la aplicación Cuando se importa la DLL, toda la información importada está cubierta, por lo que debemos acceder al nombre de la función importada y otra información obteniendo pImportDesc->OriginalFirstThunk

/ / pointer

PIMAGE_THUNK_DATA pOrigThunk = MakePtr( PIMAGE_THUNK_DATA, hModule,

pImportDesc->OriginalFirstThunk);

// Obtener el puntero de la matriz IMAGE_THUNK_DATA de pImportDesc- >FirstThunk, porque se llenó cuando se introdujo la DLL

//Toda la información importada, por lo que la intercepción real se realiza aquí

PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc->FirstThunk);

// Busca exhaustivamente en la matriz IMAGE_THUNK_DATA para encontrar lo que necesitamos interceptar

Función, ¡esta es la parte más crítica!

while (pOrigThunk->u1.Function)

{

// En su lugar, solo busca aquellas por nombre de función de función importada por número de serie

if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))

{

// Obtener el nombre de la función de la función importada

PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,

pOrigThunk->u1.AddressOfData);

//Si el nombre de la función comienza con NULL, saltar, continuar con la siguiente función

if ('\0' == pByName->Name[0])

continue;

// bDoHook se utiliza para verificar si la intercepción fue exitosa

BOOL bDoHook = FALSE;

// Verifique si la función actual es la función que necesitamos interceptar

if ( (paHookFunc.szFunc[0] = = pByName->Nombre[0]) &&

(strcmpi(paHookFunc.szFunc, (char*)pByName->Nombre) == 0))

{

// ¡Lo encontré!

if (paHookFunc.pProc)

bDoHook = TRUE;

}

if (bDoHook )

{

// Hemos encontrado la función que queremos interceptar, así que comencemos

// El Lo primero que debemos hacer es cambiar esta El estado de protección de la memoria virtual nos permite acceder a ella libremente

MEMORY_BASIC_INFORMATION mbi_thunk;

VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)) ;

_ASSERT( VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,

PAGE_READWRITE, &mbi_thunk.Protect));

//Guarde la dirección de salto correcta de la función que queremos interceptar

if (paOrigFuncs)

paOrigFuncs = (PROC)pRealThunk->u1.Function;

// Reescribe la dirección de salto de la función en la matriz IMAGE_THUNK_DATA a nuestra propia dirección de función!

// Todas las llamadas futuras a esta función del sistema por parte de todos los procesos se convertirán en llamadas a funciones escritas por nosotros mismos

pRealThunk->u1.Function = (PDWORD)paHookFunc. pProc;

//¡La operación se completó! Cambie esta memoria virtual a su estado de protección original

DWORD dwOldProtect;

_ASSERT (VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,

mbi_thunk.Protect, &dwOldProtect));

SetLastError(ERROR_SUCCESS);

return TRUE;

}

}

// Accede al siguiente elemento en IMAGE_THUNK_DATA Elemento de matriz

pOrigThunk++;

pRealThunk++;

}

devuelve VERDADERO;

}

// Implementación de la función GetNamedImportDescriptor

PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)

{

// Parámetros de detección

_ASSERT(szImportModule);

_ASSERT(hModule);

if ((szImportModule == NULL) || (hModule == NULL))

{

_ASSERT(FALSE);

SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

devuelve NULL;

}

// Obtener el encabezado del archivo Dos

PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;

// Comprobar si el encabezado del archivo MZ

if (IsBadReadPtr(pDOSHeader, sizeof ( IMAGE_DOS_HEADER)) ||

(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE))

{

_ASSERT(FALSE);

SetLastErrorEx (ERROR_INVALID_PARAMETER, SLE_ERROR);

return NULL;

}

// Obtener encabezado del archivo PE

PIMAGE_NT_HEADERS pNTHeader = MakePtr( PIMAGE_NT_HEADERS , pDOSHeader, pDOSHeader->e_lfanew);

//Detectar si es un archivo de imagen PE

if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||

(pNTHeader->Firma != IMAGE_NT_SIGNATURE))

{

_ASSERT(FALSE);

SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

return NULL;

}

// Verifique la sección de introducción del archivo PE (es decir, la sección .idata)

if (pNTHeader- >OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT ].VirtualAddress == 0)

return NULL;

// Obtiene el puntero a la sección de introducción (es decir, sección .idata)

PIMAGE_IMPORT_DESCRIPTOR

pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,

pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

// Busca exhaustivamente en el array PIMAGE_IMPORT_DESCRIPTOR para encontrar el módulo donde está la función necesita interceptar se encuentra

mientras (pImportDesc->Name)

{

PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc->Name);

if (stricmp(szCurrMod, szImportModule) == 0)

break; // ¡Encontrado! Romper bucle

// Siguiente elemento

pImportDesc++;

}

// Si no se encuentra, significa que el módulo que estamos buscando no ha sido introducido por el proceso actual.

if (pImportDesc->Name == NULL)

return NULL;

// Devuelve el descriptor del módulo (descriptor de importación) encontrado por la función

return pImportDesc;

}

// Implementación de la función IsNT()

BOOL IsNT()

{

OSVERSIONINFO stOSVI;

memset(&stOSVI, NULL, sizeof(OSVERSIONINFO));

stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

BOOL bRet = GetVersionEx(&stOSVI );

_ASSERT(TRUE == bRet);

si (FALSE == bRet) devuelve FALSO;

devuelve (VER_PLATFORM_WIN32_NT == stOSVI.dwPlatformId) ;

}

////////////////////////////////// /////////// //// Fin ////////////////////////////////// ////////////// ///////////////////////////////////// /////////////////////////////////////////////////

No sé cuántos amigos han intentado lograr la "pantalla del mouse con palabras" en el pasado. Es una tecnología llena de desafíos, y solo los amigos que la han probado pueden entender lo difícil que es. Al explorar la interceptación de funciones API, ninguno de los diversos materiales disponibles incluye códigos clave. Los puntos importantes son todos iguales. Después de escribir este artículo, MSDN está aún más pálido y débil. oculto además de IMAGE_IMPORT_DESCRIPTOR e IMAGE_THUNK_DATA Afortunadamente, mordí la bala y lo conquisté. Espero que este artículo pueda ser útil para todos.