2012-05-02 16 views
8

Estoy programando extensiones para un juego que ofrece una API para modders (de nosotros). Esta API ofrece una amplia variedad de cosas, pero tiene una limitación. La API es solo para el "motor", lo que significa que todas las modificaciones (mods) que se han lanzado en función del motor no ofrecen/tienen ningún tipo de API (específica de mod). He creado un "escáner de firmas" (nota: mi complemento se carga como una biblioteca compartida, compilada con -Compartir & -fPIC) que encuentra las funciones de interés (lo cual es fácil ya que estoy en Linux). Entonces para explicarlo, tomaré un caso específico: he encontrado la dirección para una función de interés, su encabezado de función es muy simple int * InstallRules(void);. Toma una nada (nulo) y devuelve un puntero entero (a un objeto de mi interés). Ahora, lo que quiero hacer, es crear un desvío (y recuerdo que tengo la dirección de inicio de la función), a mi propia función, que me gustaría que se comporten algo como esto:Detención y montaje en línea de GCC (Linux)

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 
} 

ahora Aquí está el trato; No tengo experiencia en lo que respecta a la función de enganche, y solo conozco muy bien el ensamblaje en línea (AT & T solamente). Los paquetes de desvío prefabricados en Internet es solo para Windows o está utilizando un método completamente distinto (es decir, precarga un dll para anular el original). Así que básicamente; ¿Qué debo hacer para seguir el camino? ¿Debo leer sobre las convenciones de llamadas (cdecl en este caso) y aprender sobre el ensamblaje en línea, o qué hacer? Lo mejor probablemente sería una clase de contenedor ya funcional para el desvío de linux. Al final, me gustaría algo tan simple como esto:

void * addressToFunction = SigScanner.FindBySig("Signature_ASfs&43"); // I've already done this part 
void * original = PatchFunc(addressToFunction, addressToNewFunction); // This replaces the original function with a hook to mine, but returns a pointer to the original function (relocated ofcourse) 
// I might wait for my hook to be called or whatever 
// .... 

// And then unpatch the patched function (optional) 
UnpatchFunc(addressToFunction, addressToNewFunction); 

entiendo que no voy a ser capaz de obtener una respuesta completamente satisfactoria aquí, pero me gustaría más que apreciar un poco de ayuda con las indicaciones para tomar , porque estoy sobre hielo delgado aquí ... He leído sobre el desvío, pero apenas hay documentación (específicamente para linux), y creo que quiero implementar lo que se conoce como 'trampolín', pero no puedo para encontrar la manera de adquirir este conocimiento.

NOTA: También estoy interesado en _thiscall, pero por lo que he leído que no es tan difícil llamar con GNU convención de llamada

+0

Solo para descartarlo, ¿ha considerado la solución no técnica, es decir * pidiendo * al autor que proporcione una API? –

+0

@KarlBielefeldt Eso sería difícil ... GoldSrc (para Half-Life) tiene unos 12 años, ¿no? –

Respuesta

12

¿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).

+0

Es por eso que amo StackOverflow. ¡No podría haber pedido una mejor respuesta! Definitivamente daré un comentario más extenso, pero primero quiero leer todos los enlaces, pero tuve que preguntar sobre la "inyección de DLL". ¿Eso es relevante para mí? Como mi plugin es un dll/so, que el 'mod'/juego carga (dinámicamente) lo que significa que solo podré poner mi código de 'enganche' en mi biblioteca en lugar de usar LD_PRELOAD? –

+0

Bien, entonces tienes un problema menor. De todos modos, ¿qué juego es eso? – kuba

+0

Estoy programando los llamados 'complementos de metamod' para el conocido juego de FPS Counter-Strike (que usa un motor llamado GoldSrc, que tiene ~ 12 años). La parte frustrante es que tengo casi todo lo que necesito. Literalmente tengo el código fuente de la función que intento conectar, puedo encontrar su dirección dinámicamente y conozco su convención de llamadas, así que estoy bastante desesperado tratando de resolver este último trozo del rompecabezas, pero no estoy listo para pasar 2 meses cavando libros de ensamblaje, uno tras otro ... –