Ir al contenido


Foto

Inyección directa de código II


  • Por favor identifícate para responder
8 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 marzo 2010 - 05:36

Aunque la inyección de código no suele ser un tema de frecuente utilización me ha parecido interesante realizar una segunda parte del tutorial "Inyección directa de código en C y en delphi", con un reto técnico. Se trata de realizar la inyección de una función de forma directa, como hice en la primera parte de este tutorial pero poniendo en practica una idea que no es nueva. Lo que si es nueva es la forma de llevarla a la práctica.

La idea es la siguiente. Podemos suspender un Thread con la API SuspendThread y luego reanudar su ejecución con ResumeThread. Si somos capaces de guardar el puntero de instrucción del procesador EIP y sustituir su valor por el de nuestro código, haremos que al reanudar la ejecución del Thread en cuestión se ejecute nuestro código.

Como he dicho, esta idea no es nueva. El problema que plantéa es que el código inyectado debe ser un shellcode, es decir un fragmento de código asm compuesto casi por opcodes y con la capacidad de funcionar aislado por si mismo. Esto nos limita mucho y nos obliga a programar con un bajo nivel importante.

Lo que propongo como novedad es conseguir que nuestro Shellcode no sea tan limitante y que nos permita alguna importante licencia como es poder implementar parte de él en C. El resto de las limitaciones serán similares a la técnica expuesta en la primera parte con la ya conocida API CreateRemoteThread.



Saludos.

Archivos adjuntos


  • 0

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 marzo 2010 - 05:38

Bueno vamos al grano que es lo que interesa.

En primer lugar veamos la forma de parar un Thread capturar el EIP y reanudar la ejecución del Thread engañando al procesador:
 

cpp
  1. //Suspendemos el hilo y tomamos el puntero de instrucciones eip
  2. SuspendThread(hThread);
  3. Context.ContextFlags = CONTEXT_CONTROL;
  4. GetThreadContext(hThread, &Context);
  5.  
  6. //modificamos el eip para que apunte al código inyectado
  7. Context.Eip = (DWORD)Mi_Funcion_inyectada;
  8. Context.ContextFlags = CONTEXT_CONTROL;
  9. SetThreadContext(hThread, &Context);
  10.  
  11. //Le decimos al hilo que puede volver a ejecutarse (lanzará nuestro codigo)
  12. ResumeThread(hThread);

Fácil, ¿no?. El verdadero problema radica en escribir Mi_Funcion_inyectada y el paso de parámetros necesarios para ésta. Los parámetros los pasaremos en una estructura. En ella tendremos que introducir los datos y direcciones de las APIs que usará el código inyectado. En este caso realizaremos también la liberación de una DLL y aprovecharemos para mostrar también un cuadro de mensaje desde el proceso "victima".
Pero, a diferencia del caso expuesto en la primera parte del tutorial, este código debe ser un shellcode autónomo, ya que simplemente se ejecutará porque vamos a cambiar el puntero de instrucciones del procesador el registro EIP. Esto quiere decir que nuestra función no recibe parámetros y al acabar no sabe donde retornar, dos circunstancias catastróficas para el proceso inyectado y que debemos resolver.

La dirección de retorno la podemos saber con la API SetThreadContext ese dato deberá ser colocado en nuestro código directamente, es decir en el binario o en sus opcodes. Los parámetros los colocaremos inmediatamente detrás del código inyectado, así podremos localizarlos fácilmente desde él.

Visto así parece complicado aunque un poco mas claro que al principio ¿no?. Pero seguimos con el problema del manejo a bajo nivel de toda la función inyectada, en la que no podremos declarar variables locales y deberemos manejar en asm. Vamos a ver si le incamos el diente a este asunto y nos lo ponemos un poco más fácil. Vamos a realizar nuestro propio "precompilador", por llamarlo de alguna manera para poder escribir parte del código de nuestro shellcode en C que será inyectado como binario puro en tiempo de ejecución.

Vamos a ir colocando código ya encamimado a la cuestión práctica, pero aclarar un detalle importante tenemos que forzar al compilador a una alineación DoubleWord (8 bytes) en todo lo que vamos a inyectar, estructura de datos y shellcode. Para eso usaremos la directiva adecuada.

Digimos que nuestra función inyectada iba a hacer dos cosas, un MessageBox y liberar una dll. Pues nuestra estrucura de datos contendra las cadenas de la ruta de la dll y el caption del MessageBox a mostrar. Este MessageBox mostrará el texto del Path de la dll a liberar. También contendrá las direcciones de las APIs usadas. Dado que no estamos inyectando un Thread, sino un Shellcode no nos sirve el uso de WaitForSingleObject y GetExitCodeThread para controlar la finalización del código y su resultado de vuelta, por lo que usaremos unos valores de retorno que nos indicarán si la función terminó de ejecutarse (Terminado) y el resultado de la misma (Result).

cpp
  1. //La estructura que inyectaremos
  2. struct TParamFL
  3. {
  4.     char  DllName[256];
  5.     char  Caption[256];
  6.     PGetModuleHandleA GetModuleHandleA;
  7.     PFreeLibrary FreeLibrary;
  8.     BOOL  Result;      // Retornará el resultado
  9.     BOOL  Terminado;  // Indicará que el código se ha ejecutado
  10.     PMessageBoxA MessageBoxA;
  11. };

Saludos.
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 marzo 2010 - 05:40

Ahora viene lo duro, vamos a ver la función que inyectaremos:


cpp
  1. // Función que inyectaremos para liberar dlls
  2. // ¡¡¡  No cambiar de sitio  !!!
  3. //
  4. // La idea es que se copiará en un bloque a parte, en la memoria del proceso
  5. // víctima, la función shellcode seguida de la estructura de parámetros.
  6. // Luego con suspensión de thread se saltará a la entrada de la función, quien
  7. // manejará los parámetros como una referencia a la estructura.
  8. // Cualquier API se usará por un puntero pasado en la estructura de parámetros,
  9. // como en la inyección directa con CreateRemoteThread
  10. //
  11. // Se debe definir un mínimo de espacio de pila para las variables que se creen
  12. // en la funcion
  13.  
  14. # define Pila 100
  15.  
  16. // Esta función de ejecuta en el proceso víctima como un Shellcode
  17.  
  18. __declspec(naked)
  19. void InjFreeLibraryST()
  20. {
  21.   DWORD BFunc;      // Variables locales que usaremos !que bien no todo es asm!
  22.   DWORD BParam;
  23.   asm{
  24.   ini:
  25.   push 0x12345678    // Dirección de retorno de este código
  26.   pushfd
  27.   pushad
  28.   mov ebp, esp
  29.   add esp, -Pila    // Reservo pila para variables creadas, se puede reservar mas...
  30.   call sigue
  31.   sigue:
  32.   pop eax            // recupero el registro Eip
  33.   sub eax, sigue-ini // eax apunta ahora al inicio de la función
  34.   mov BFunc, eax
  35.   mov ebx, fin - ini // Calculamos el tamaño de la función
  36.   dec ebx            // Ajustado a múltiplo de 4 (DWORD),
  37.   shr ebx, 2        // para poder calcular la posición de los parámetros
  38.   inc ebx            // que estarán al final de la función
  39.   shl ebx, 2        //
  40.   add eax, ebx      //
  41.   mov BParam, eax    // Puntero al Inicio de la función una vez inyectada
  42.   }
  43.  
  44.   // Preparación del acceso a los parámetros:
  45.   // Los parámetros estarán al final de la función
  46.  
  47.   // En este ejemplo, habiendo reservado pila suficiente, podemos crear variables
  48.   // y actuar por referencia o con variables puntero...
  49.   TParamFL& P = *(TParamFL*)(BParam); // Referencia a la estructura
  50.   TParamFL* pP = (TParamFL*)(BParam); // Puntero a la estructura
  51.  
  52.   // A continuación escribimos el Código inyectado
  53.   // ¡ ya podemos escribir en C/C++ !:
  54.  
  55.   // prueba simple con un mensaje:
  56.   P.MessageBoxA(0, P.DllName, P.Caption, 0);
  57. //  pP->MessageBoxA(0, pP->DllName, pP->Caption, 0);  // se nos permite esta otra forma de hacer lo mismo
  58.  
  59.   // Descarga de una Dll
  60.   // Si retorna true, el módulo no existe en el proceso.
  61.   // En caso contrario, sigue cargado
  62.   HMODULE hModule = P.GetModuleHandleA(P.DllName);  //encontramos el hModule de la dll
  63.   P.FreeLibrary(hModule);    // Elecutamos la API FreeLibrary que descarga la Dll
  64.  
  65.   P.Result = (P.GetModuleHandleA(P.DllName)==0);
  66.   P.Terminado = true;
  67.  
  68.   // Pero antes de terminar volvemos al asm...
  69.   // Ajustes de pila y retorno al Eip que pusimos en la pila
  70.   asm{
  71.   add esp, Pila
  72.   popad
  73.   popfd
  74.   ret  // Salta al Eip
  75. fin:    // Final de la función e inicio de parámetros
  76.   }
  77. }

Como dato previo he de comentar la forma de declarar la función como __declspec(naked) esto es para que el copilador no introduzca ningún código adicional a la función, así nosotros seremos loa responsables de reservar pila y de retornar. Esta forma de declarar funciones es muy poco usual y se usa fundamentalmente para código asm puro sin añadiduras por parte del compilador.

Tres cosas importantes en el código. Lo primero que hace es apilar la dirección de retorno, puesta en el mismo código por la función inyectora que veremos mas tarde. Esto permitirá retornar al proceso original con un simple asm ret.

La segunda cosa importante es reservar un espacio de Pila para poder crear variables locales, ese espacio debe ser suficiente para alojarlas, esto será la segundo que haga nuestro código inyectado por supuesto todavía programando en asm.

La tercera labor será encontrar los parámetros que se le pasan. Tradicionalmente esto se hace en la pila pero para nuestro shellcode nosotros los vamos a pasar al final de la función. Esto nos obliga a calcular donde termina. Es facil, con etiquetas asm colocadas estratégicamente al inicio y fin sabemos lo que ocupa la función. Ese valor lo ajustaremos a múltiplo de 4 (por la alineación del código) y al resultado le añadimos el valor del EIP en la etiqueta considerada inicio con lo que tenemos el valor absoluto dela localización de los parámetros que buscamos.

Para calcular el EIP usamos el viejo truco de la dirección de retorno en pila al realizar una instrucción call, sólo que vamos a realizar un call a la siguiente instrucción:

cpp
  1.   call sigue
  2.   sigue:
  3.   pop eax            // recupero el registro Eip

Hecho todo esto podemos escribir el resto del código en C/C++ y declarar variables locales, aunque no podremos inicializar cadenas, ya que esto lo hace el compilador en la pila del proceso que compilamos, que es distinta a la del proceso sobre el que nos inyectaremos. Para finalizar el código tenemos que volver al asm, con el fin de restaurar la pila y retornar al proceso inyectado. Tras finalizar, "aquí no ha pasado nada", el proceso inyectado sigue en el punto que fue interrumpido, quizás con un pequeño retraso derivado del tiempo que lo mantuvimos dormido.
Como podéis ver en la función de ejemplo, podemos declarar variables locales a nuestro gusto (estilo C++) y manejar punteros o referencias según nos convengan. No podemos inicializar variables que lo hagan en la pila, como las cadenas. Por supuesto no debemos olvidar colocar los valores de retorno, que son dos, una bandera que indica que la función terminó y el valor de retorno propiamente dicho.



Saludos.
  • 0

#4 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 marzo 2010 - 05:49

Ahora le toca el turno a la función inyectora. Si os fijais, esta función está declarada como si fuese la función que se inyecta, es decir recibe los parápetros que desearíamos pasar al código inyectado y nos devuelve el resultado de la inyección. Realmente esta función es un interface entre nuestro proceso y el proceso en el que nos vamos a inyectar. Veamos como funciona:



cpp
  1. // Función que inyecta y ejecuta InjFreeLibrary
  2. // ¡¡¡  No cambiar de sitio  !!!
  3. BOOL FarFreeLibraryST(DWORD Pid, char *DllName)
  4. {
  5.   POpenThread OpenThread;
  6.   HANDLE      hProcess;  // El handle del proceso en el que inyectaremos
  7.   HANDLE      hThread;    // El handle del thread principal del proceso
  8.   CONTEXT    Context;
  9.   DWORD      Tid = 0;
  10.  
  11.   DWORD      OldProtect;
  12.  
  13.   TParamFL    Param;      // El tipo de dato de la estructura
  14.   DWORD      SizeInject; // Tamaño total a inyectar Parametros + Función
  15.   UINT        TamFun;    // El tamaño de la función a inyectar
  16.   void*      BFunc;      // Lugar de memoria donde copiaremos nuestra función
  17.   void*      BParam;    // Lugar de memoria donde copiaremos los parámetros
  18.   void*      BShell;    // Bloque completo del Shellcode
  19.  
  20.   //Abrimos el proceso en el que nos inyectaremos
  21.   hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  22.   if(!hProcess) return 0;
  23.  
  24.   //Abrimos el hilo
  25.   OpenThread = (POpenThread)GetProcAddress(GetModuleHandle("kernel32.dll"), "OpenThread");
  26.   Tid = GetFirstThreadID(Pid);
  27.   hThread = OpenThread(THREAD_ALL_ACCESS, false, Tid);
  28.   if(!hThread) return 0;
  29.  
  30.   //Metemos la dirección de las apis en la estructura
  31.   Param.Result = false;
  32.   Param.Terminado = false;
  33.   Param.FreeLibrary = (PFreeLibrary)GetProcAddress(GetModuleHandle("KERNEL32.DLL"), "FreeLibrary");
  34.   Param.GetModuleHandleA = (PGetModuleHandleA)GetProcAddress(GetModuleHandle("KERNEL32.DLL"), "GetModuleHandleA");
  35.   Param.MessageBoxA = (PMessageBoxA)GetProcAddress(GetModuleHandle("user32.dll"), "MessageBoxA");
  36.   strcpy(Param.Caption, "Eureca");
  37.   strcpy(Param.DllName, DllName);
  38.  
  39.   //Calculamos el tamaño de la función a inyectar y nos damos permisos de escritura...
  40.   TamFun = (UINT)FarFreeLibraryST - (UINT)InjFreeLibraryST;
  41.   SizeInject = TamFun + sizeof(TParamFL); //Tamaño de la función mas los parámetros.
  42.   VirtualProtectEx((void*)-1, InjFreeLibraryST, TamFun, PAGE_EXECUTE_READWRITE, &OldProtect);
  43.  
  44.   //Suspendemos el hilo y tomamos el puntero de instrucciones eip
  45.   SuspendThread(hThread);
  46.   Context.ContextFlags = CONTEXT_CONTROL;
  47.   GetThreadContext(hThread, &Context);
  48.   //colocamos el EIP en nuestro código como parte del opcode de la primera instrucción push
  49.   *(DWORD*)((DWORD)InjFreeLibraryST+1) = Context.Eip;
  50.  
  51.   //Reservamos memoria y escribimos nuestro código en él
  52.   BShell = VirtualAllocEx(hProcess, NULL, SizeInject, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
  53.   BFunc = BShell;
  54.   BParam = (void*)((DWORD)BShell + TamFun);
  55.   WriteProcessMemory(hProcess, BFunc, InjFreeLibraryST, TamFun, NULL);  //la función
  56.   WriteProcessMemory(hProcess, BParam, &Param, sizeof(TParamFL), NULL); //los parámetros
  57.  
  58.   //Modificamos el eip para que apunte al código inyectado
  59.   Context.Eip = (DWORD)BFunc;
  60.   Context.ContextFlags = CONTEXT_CONTROL;
  61.   SetThreadContext(hThread, &Context);
  62.  
  63.   //Le decimos al hilo que puede volver a ejecutarse (lanzará nuestro codigo)
  64.   ResumeThread(hThread);
  65.  
  66.   // Provoco la ejecución del thread por parte de algún procesador para que se
  67.   // ejecute el Shellcode y devuelva el resultado.
  68.   PostThreadMessage(Tid, 0,0,0);
  69.  
  70.   // Leo el bloque de parámetros inyectado para conocer Result
  71.   // Debo esperar a que la rutina se ejecute: Param.Terminado == true
  72.   do{
  73.     ReadProcessMemory(hProcess, BParam, &Param, sizeof(TParamFL), NULL);
  74.   }while(!Param.Terminado);
  75.  
  76.   // Limpieza
  77.   VirtualFree(BShell, 0, MEM_RELEASE);
  78.  
  79.   CloseHandle(hProcess);
  80.   CloseHandle(hThread);
  81.  
  82.   return Param.Result;
  83. }



Esta función deberá encargarse de:
1.- Preparar los parámetros y el código a inyectar para colocar ambos en el espacio de direcciones del proceso "victima".
2.- Localizar un thread seguro, pararlo, realizar la inyección y arrancar nuestro código reanudando el thread congelado.
3.- Comprobar que nuestro código inyectado ha terminado y leer resultados del mismo si los hubiere.
4.- Finalmente deberá realizar las labores de limpieza pertinentes.

El punto 1 es sencillo, fue visto en la primera parte de este tutorial y se basa en el uso de la API WriteProcessMemory. En este caso colocaremos la estructura de parámetros inmediatamente detrás de la función inyectada, por lo que lo haremos en el punto 2.
El punto 2 debe congelar un thread seguro, para lo que localizamos el primero del proceso a inyectar. Lo paramos, guardamos el EIP en el código binario de la primera instrucción de nuestro código inyectado que será una instrucción push. Con esto aseguramos el retorno. Es ahora cuando concluimos el punto 1 y podemos subir al espacio de direcciones del proceso a inyectar, el código y la estructura de datos. Después cambiamos el EIP para que apunte a nuestro código, ya en el espacio de direcciones del proceso víctima. Seguidamente volvemos a permitir que se ejecute el thread, con lo que se ejecutará nuestro código. Para asegurarnos de que se reanuda la ejecución del thread usaremos la API PostThreadMessage.
El punto 3 se desarrolla en un bucle de espera hasta que podamos leer la bandera de finalización en el espacio de direcciones del proceso inyectado, en la misma estructura de datos que allí colocamos. Una vez terminado, leeremos igualmente el resultado de vuelta de nuestro código.
El punto 4 libera la memoria inyectada cierra los Handles abiertos al proceso y al thread y retorna el resultado del código inyectado.

Las funciones GetFirstThreadID y GetProcessId no son API. La primera busca el primer Thread de un proceso y sobre ese vamos a trabajar. La segunda realmente no nos hace falta, sólo la uso para buscar el Pid de un proceso sabiendo su nombre (devuelve el primer proceso que encuentra con ese nombre). La uso en el ejemplo.

Un poco lioso pero espero que se entienda bien.


Saludos.
  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 marzo 2010 - 05:51

Ahora vamos a hacer un ejemplo práctico simple:
 

cpp
  1. int main(int argc, char* argv[])
  2. {
  3.   char DllName[] = "C:\\UnaDll.dll";
  4.   bool I = FarFreeLibraryST(GetProcessId("Notepad.exe"), DllName);
  5.   return 0;
  6. }

Subo el código
Esto es todo, espero que pueda ser de utilidad.



Saludos.
  • 0

#6 azdin

azdin

    Member

  • Miembros
  • PipPip
  • 20 mensajes

Escrito 18 octubre 2015 - 09:33

Hola

 

Podrías subir este ejemplo compilado para solo indicar la dirección de la Dll y el nombre o PID del proceso.

Por otro lado me podría decir que compilador e IDE usa?


  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 19 octubre 2015 - 01:54

Hola

 

Podrías subir este ejemplo compilado para solo indicar la dirección de la Dll y el nombre o PID del proceso.

Por otro lado me podría decir que compilador e IDE usa?

 

El compilador usado fue Borland C++ Builder 5.

 

En una de las mudanzas del foro se perdió el archivo adjunto. Lo he localizado y lo he subido en el primer mensaje de este tema.


  • 1

#8 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 477 mensajes

Escrito 20 octubre 2015 - 10:56

lo que no alcanzo a entender bien es a que te referis con inyeccion de codigo. Corregime si me equivoco.

Las dll son como unidades contienen codigo funcional que puede ser cualquier cosa como una suma. La diferencia es que son dinamicas, sirven para que, cualquier lenguaje que incorpore dll puede sumar 2 numeros...

Por inyeccion "directa" te referis a pasar codigo c++ a una app delphi y ejecutarlo?

Y como se podria lograr que cualquier funcion programable en c++ (algo mas bajo que delphi) pueda ser ejecutada?


  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 20 octubre 2015 - 12:54

Inyectar código en un proceso cualquiera significa colocar dicho código en el espacio de memoria del proceso y obligar su ejecución.

La inyección indirecta obliga al proceso atacado a cargar una dll que no esperaba, esto lo hace el S.O. Al cargar una dll siempre se ejecuta su punto de entrada, hecho que aprovechamos para ejecutar cosas.

La inyección directa, carga el código "a pelo" en el proceso atacado y lo ejecuta. En esto no colabora el S.O., es más compleja que la inyección de dll pues no sirve cualquier código, básicamente debe ser autónomo, reubicable y, si usa API, debe encontrar las funciones por si mismo. A este tipo especial de códico se le denomina shellcode.

No tiene nada que ver con el lenguaje usado. De forma que podemos inyectar en un proceso no escrito en delphi, e incluso, en el mismo S.O.


Saludos.
  • 0




IP.Board spam blocked by CleanTalk.