2012-05-05 18 views
7

Creo que mi pregunta puede parecer un poco extraña, pero aquí va; Intento crear un programa dinámicamente en C++ (principalmente por diversión, pero también por una razón programática) y no es tan difícil como podría parecer. Para ello hay que utilizar asamblea en tiempo de ejecución de la siguiente manera:Un patrón válido en ensamblaje para argumentos variados

byte * buffer = new byte[5]; 
*buffer = '0xE9'; // Code for 'jmp' 
*(uint*)(buffer + 1) = 'address destination'; // Address to jump to 

Esto es mucho más fácil de lo que parece, ya que me meta una sola plataforma y el compilador; GCC con Linux 32bit (y también solo uno convención de llamadas, cdecl). Así que estoy tratando de crear una función de ensamblaje dinámico para redirigir las llamadas desde los desencadenadores, por lo que puedo utilizar los métodos de clase como devoluciones de llamada (incluso con bibliotecas de API C (con cdecl, por supuesto)). Solo necesito esto para admitir punteros y tipos nativos (char, int, short, etc.).

ANYTHING MyRedirect(ANY AMOUNT ARGUMENTS) 
{ 
    return MyClassFunc('this', ANY AMOUNT ARGUMENTS); 
} 

La función anterior, es la que quiero crear en ensamblaje puro (en memoria con C++). Como la función es muy simple, su ASM también es simple (según los argumentos).

55      push %ebp 
89 e5     mov %esp,%ebp 
83 ec 04    sub $0x4,%esp 
8b 45 08    mov 0x8(%ebp),%eax 
89 04 24    mov %eax,(%esp) 
e8 00 00 00 00   call <address> 
c9      leave 
c3      ret 

Así que en mi programa, me han creado un generador de patrones de ASM (ya no sé ASM especialmente bien, busco patrones). Esta función puede generar código de ensamblado (en bytes, para el caso exacto anterior, es decir, una función que redirige y devuelve) especificando la cantidad de argumentos que necesita la función. Este es un fragmento de mi código C++.

std::vector<byte> detourFunc(10 + stackSize, 0x90); // Base is 10 bytes + argument size 

// This becomes 'push %ebp; move %esp, %ebp' 
detourFunc.push_back(0x55);  // push %ebp 
detourFunc.push_back(0x89);  // mov 
detourFunc.push_back(0xE5);  // %esp, %ebp 

// Check for arguments 
if(stackSize != 0) 
{ 
    detourFunc.push_back(0x83);  // sub 
    detourFunc.push_back(0xEC);  // %esp 
    detourFunc.push_back(stackSize); // stack size required 

    // If there are arguments, we want to push them 
    // in the opposite direction (cdecl convention) 
    for(int i = (argumentCount - 1); i >= 0; i--) 
    { 
     // This is what I'm trying to implement 
     // ... 
    } 

    // Check if we need to add 'this' 
    if(m_callbackClassPtr) 
    { 

    } 
} 

// This is our call operator 
detourFunc.push_back(0xE8);  // call 

// All nop, this will be replaced by an address 
detourFunc.push_back(0x90);  // nop 
detourFunc.push_back(0x90);  // nop 
detourFunc.push_back(0x90);  // nop 
detourFunc.push_back(0x90);  // nop 

if(stackSize == 0) 
{ 
    // In case of no arguments, just 'pop' 
    detourFunc.push_back(0x5D); // pop %ebp 
} 

else 
{ 
    // Use 'leave' if we have arguments 
    detourFunc.push_back(0xC9); // leave  
} 

// Return function 
detourFunc.push_back(0xC3);  // ret 

Si puedo especificar cero como el stackSize esta será la salida:

55      push %ebp 
89 e5     mov %esp,%ebp 
e8 90 90 90 90   call <address> 
5d      pop %ebp 
c3      ret 

Como se puede ver, esto es completamente válida ASM de 32 bits, y actuará como el 'MyRedirect' si tenía cero argumentos y no era necesario un puntero 'this'. El problema es que quiero implementar la parte donde genera el código ASM, dependiendo de la cantidad de argumentos que especifique que recibirá la función 'redirigir'. He hecho esto con éxito en mi pequeño programa C++ (craqueado el patrón).

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char * argv[]) 
{ 
    int val = atoi(argv[1]); 

    printf("\tpush %%ebp\n"); 
    printf("\tmov %%esp,%%ebp\n"); 

    if(val == 0) 
    { 
     printf("\tcall <address>\n"); 
     printf("\tpop %%ebp\n"); 
    } 

    else 
    { 
     printf("\tsub $0x%x,%%esp\n", val * sizeof(int)); 

     for(int i = val; i > 0; i--) 
     { 
      printf("\tmov 0x%x(%%ebp),%%eax\n", i * sizeof(int) + sizeof(int)); 
      printf("\tmov %%eax,0x%x(%%esp)\n", i * sizeof(int) - sizeof(int)); 
     } 

     printf("\tcall <address>\n"); 
     printf("\tleave\n"); 
    } 

    printf("\tret\n"); 
    return 0; 
} 

Esta función imprime exactamente el mismo patrón que el código ASM generado por 'objdump'. Entonces mi pregunta es; ¿Será esto válido en todos los casos si I solo desea una función de redirección como la de arriba, sin importar los argumentos, si solo está bajo Linux 32 bits, o hay algún error que deba conocer? Por ejemplo; ¿sería el ASM generado diferente con 'shorts' o 'chars' o funcionará (solo he probado con enteros), y también si invoco una función que devuelve 'void' (¿cómo afectaría eso a ASM)?

que podría haber explicado todo un poco borrosa, de manera que pregunte en lugar de cualquier malentendido :)

NOTA: No quiero saber las alternativas, me gusta mi aplicación actual y creo que es muy interesante, me agradecería mucho su ayuda en el tema.

EDIT: En caso de interés, aquí hay algunos vertederos de lo anterior código C++: link

+6

Una muy buena alternativa a las instrucciones de codificación a mano es la biblioteca [asmjit] (http://code.google.com/p/asmjit/) (al contrario del nombre, este no es un compilador JIT, simplemente algo que Compiladores JIT pueden usar). –

+0

@afishwhoswimsaround Esa biblioteca se veía increíble. Literalmente. Eso parece ser _exactamente_ lo que necesito. Pregunta rápida; ¿Puedo crear una función de forma dinámica con esta biblioteca (con malloc o algo así) para poder 'redirigir' (con el ensamblaje 'jmp') otras funciones ('engancharlas') a las funciones generadas? –

+1

Sí, mira los ejemplos http://code.google.com/p/asmjit/wiki/Examples –

Respuesta

1

Como Dan sugiere, es necesario marcar la memoria como ejecutable. I wrote some code you can use. (Funciona en GNU/Linux y Windows.) Si intentas nunca soportar ARM, x86-64 u otras plataformas, entonces no veo ninguna caída en tu código (con la parte ejecutable agregada) y parece que debería "siempre funcionar". (Suponiendo que todo lo demás funciona correctamente, por supuesto.)

#include <sys/mman.h> 

... 

n = <size of code buffer>; 
p = mmap(0, n, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, 0, 0); 

'fish' sugirió que utilice asmjit. Debo estar de acuerdo con eso; es más portátil que tu método. Sin embargo, dijiste que no estás interesado en alternativas.

Puede que le interese algo llamado "Thunking" (tipo de). Básicamente, trata de lograr el "reemplazo de una devolución de llamada C con un método C++". Esto es bastante útil, pero no es realmente un buen diseño para sus aplicaciones.

Espero que ayude.

Cuestiones relacionadas