¿Este proyecto para desarrollar un "marco" que (?) permitirá a otros conectar diferentes funciones en diferentes binarios? ¿O es solo que usted necesita enganchar este programa específico que tiene?
Primero, supongamos que quieres lo segundo, solo tienes una función en un binario que deseas enganchar, de forma programática y confiable. El principal problema al hacer esto universalmente es que hacer esto de manera confiable es un juego muy difícil, pero si estás dispuesto a hacer algunos compromisos, entonces definitivamente es factible. También supongamos que esto es algo x86.
Si desea conectar una función, hay varias opciones de cómo hacerlo. Lo que hace Detours es parche en línea. Tienen una buena visión general de cómo funciona en un Research PDF document. La idea básica es que tienes una función, p.
00E32BCE /$ 8BFF MOV EDI,EDI
00E32BD0 |. 55 PUSH EBP
00E32BD1 |. 8BEC MOV EBP,ESP
00E32BD3 |. 83EC 10 SUB ESP,10
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998]
...
...
Ahora se sustituye el principio de la función con una llamada o JMP a su función y guardar los bytes originales que ha sobrescrito con el parche en alguna parte:
00E32BCE /$ E9 XXXXXXXX JMP MyHook
00E32BD3 |. 83EC 10 SUB ESP,10
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998]
(Tenga en cuenta que sobreescribí 5 bytes .) Ahora se llama a su función con los mismos parámetros y la misma convención de llamadas que la función original.Si su función quiere llamar a la original (pero no tiene que ser así), crea un "trampolín", que 1) ejecuta las instrucciones originales que se sobrescribieron 2) jmps para el resto de la función original:
Trampoline:
MOV EDI,EDI
PUSH EBP
MOV EBP,ESP
JMP 00E32BD3
Y eso es todo, solo necesita construir la función de trampolín en tiempo de ejecución emitiendo instrucciones del procesador. La parte más difícil de este proceso es hacer que funcione de manera confiable, para cualquier función, para cualquier convención de llamadas y para diferentes sistemas operativos/plataformas. Uno de los problemas es que si los 5 bytes que desea sobreescribir terminan en el medio de una instrucción. Para detectar "fines de instrucciones", básicamente necesitaría incluir un desensamblador, porque puede haber instrucciones al principio de la función. O cuando la función es en sí misma más corta que 5 bytes (una función que siempre devuelve 0 se puede escribir como XOR EAX,EAX; RETN
que tiene solo 3 bytes).
La mayoría de los compiladores/ensambladores actuales producen un prólogo de función larga de 5 bytes, exactamente para este propósito, enganchar. ¿Ves eso MOV EDI, EDI
? Si te preguntas, "¿por qué diablos se mueven edi a edi? ¡Eso no hace nada !?" tiene toda la razón, pero este es el propósito del prólogo, tener exactamente 5 bytes de longitud (no termina en el medio de una instrucción). Tenga en cuenta que el ejemplo de desmontaje no es algo que inventé, es calc.exe en Windows Vista.
El resto de la implementación del gancho es solo detalles técnicos, pero pueden darte muchas horas de dolor, porque esa es la parte más difícil. También el comportamiento que se describe en su pregunta:
void MyInstallRules(void)
{
if(PreHook() == block) // <-- First a 'pre' hook which can block the function
return;
int * val = InstallRules(); // <-- Call original function
PostHook(val); // <-- Call post hook, if interest of original functions return value
}
parece peor que lo que he descrito (y lo hace desvíos), por ejemplo, es posible que desee "no llame a la original", pero volver algún valor diferente. O llama a la función original dos veces. En cambio, deje que su manejador de gancho decida si llamará a la función original y dónde lo hará. Además, no necesita dos funciones de controlador para un gancho.
Si no tiene suficiente conocimiento sobre las tecnologías que necesita para esto (principalmente ensamblaje), o no sabe cómo enganchar, le sugiero que estudie lo que hace Detours. Enganche su propio binario y tome un depurador (OllyDbg por ejemplo) para ver a nivel de ensamblaje qué hizo exactamente, qué instrucciones se colocaron y dónde. También this tutorial puede ser útil.
De todos modos, si su tarea es enganchar algunas funciones en un programa específico, entonces esto es factible y si tiene algún problema, simplemente pregunte aquí de nuevo. Básicamente puedes hacer muchas suposiciones (como los prólogos de funciones o convenciones usadas) que harán tu tarea mucho más fácil.
Si desea crear un marco de enganche confiable, entonces todavía es una historia completamente diferente y primero debe comenzar creando simples enganches para algunas aplicaciones simples.
También tenga en cuenta que esta técnica no es específica del sistema operativo, es la misma en todas las plataformas x86, funcionará tanto en Linux como en Windows. Lo que es OS específico es que probablemente tendrá que cambiar la protección de la memoria del código ("desbloquearlo", para que pueda escribir), que se hace con mprotect
en Linux y con VirtualProtect
en Windows. Además, las convenciones de llamada son diferentes, eso es lo que puedes resolver usando la sintaxis correcta en tu compilador.
Otro problema es "inyección DLL" (en Linux probablemente se llamará "inyección de biblioteca compartida" pero el término inyección DLL es ampliamente conocido). Necesita poner su código (que realiza el enlace) en el programa. Mi sugerencia es que, si es posible, solo use la variable de entorno LD_PRELOAD, en la que puede especificar una biblioteca que se cargará en el programa justo antes de que se ejecute.Esto se ha descrito en TAN muchas veces, como aquí: What is the LD_PRELOAD trick?. Si debe hacer esto en tiempo de ejecución, me temo que tendrá que obtener con gdb o ptrace, que en mi opinión es bastante difícil (al menos lo más importante) que hacer. Sin embargo, puede leer, por ejemplo, this article on codeproject o this ptrace tutorial.
También encontré algunos recursos bonito:
Otro punto más: este "parche en línea" no es la única forma de hacerlo. Hay formas incluso más simples, p. si la función es virtual o si es una función exportada de la biblioteca, puede omitir todo el ensamblaje/desensamblaje/JMP y simplemente reemplazar el puntero a esa función (en la tabla de funciones virtuales o en la tabla de símbolos exportados).
Solo para descartarlo, ¿ha considerado la solución no técnica, es decir * pidiendo * al autor que proporcione una API? –
@KarlBielefeldt Eso sería difícil ... GoldSrc (para Half-Life) tiene unos 12 años, ¿no? –