2010-07-17 17 views
25

Voy a explicar:¿Puede gdb hacer que un puntero a otra posición funcione?

Digamos que estoy interesado en reemplazar la función rand() utilizada por una determinada aplicación.

Así que adjuntar gdb a este proceso y que se cargue mi biblioteca compartida personalizada (que tiene una función personalizada rand()):

call (int) dlopen("path_to_library/asdf.so") 

Esto colocaría la medida rand() función dentro de la memoria del proceso. Sin embargo, en este punto, el símbolo rand seguirá apuntando a la función predeterminada rand(). ¿Hay alguna manera de hacer que gdb apunte el símbolo a la nueva función rand(), forzando al proceso a usar mi versión?

Debo decir que tampoco estoy autorizado a utilizar los métodos LD_PRELOAD (linux) ni DYLD_INSERT_LIBRARIES (mac os x) para esto, ya que permiten la inyección de código solo al principio de la ejecución del programa.

La aplicación que me gustaría reemplazar rand(), inicia varios hilos y algunos de ellos comienzan nuevos procesos, y me interesa inyectar código en uno de estos nuevos procesos. Como mencioné anteriormente, GDB es excelente para este propósito porque permite la inyección de código en un proceso específico.

+0

son los subprocesos de interés simplemente copias del original bifurcadas, o lo hacen también 'exec()' nuevas imágenes? – llasram

+0

@llasram no son copias ahorquilladas. – karlphillip

+0

Otra pregunta: ¿tiene alguna restricción de versión 'gdb', o podría instalar y usar una versión arbitrariamente reciente? – llasram

Respuesta

11

que siguieron this post y this presentation y se acercó con el siguiente conjunto de comandos gdb para OSX con x86-64 ejecutable, que puede ser cargado con -x opción para la conexión con el proceso:

set $s = dyld_stub_rand 
set $p = ($s+6+*(int*)($s+2)) 
call (void*)dlsym((void*)dlopen("myrand.dylib"), "my_rand") 
set *(void**)$p = my_rand 
c 

La magia es en el comando set $p = .... dyld_stub_rand es una instrucción de salto de 6 bytes. El desplazamiento de salto está en dyld_stub_rand+2 (4 bytes). Se trata de un salto -respecto $rip, por lo que añadir a lo que compensa $rip sería en este momento (justo después de la instrucción, dyld_stub_rand+6).

Esto apunta a una entrada de tabla de símbolos, que debe ser rand real o rutina de enlazador dinámico para cargarla (si nunca se llamó). Luego se reemplaza por my_rand.

A veces gdb recogerá dyld_stub_rand de libSystem u otra biblioteca compartida, si eso sucede, descárguelos primero con remove-symbol-file antes de ejecutar otros comandos.

2

No estoy seguro de cómo hacer esto en un programa en ejecución, pero quizás LD_PRELOAD funcionará para usted. Si establece esta variable de entorno en una lista de objetos compartidos, el cargador de tiempo de ejecución cargará el objeto compartido al principio del proceso y permitirá que las funciones en él tengan prioridad sobre las demás.

LD_PRELOAD=path_to_library/asdf.so path/to/prog 

Usted tiene que hacer esto antes de comenzar el proceso, pero que no tiene que reconstruir el programa.

9

Esta pregunta me intrigó, así que investigué un poco. Lo que estás buscando es un 'dll injection'. Usted escribe una función para reemplazar alguna función de la biblioteca, la coloca en .so y le dice a ld que precargue su dll. ¡Lo probé y funcionó muy bien! Me doy cuenta de que esto realmente no responde a su pregunta en relación con gdb, pero creo que ofrece una solución viable.

Para una solución IAE-solamente, ver mi other solution.


// -*- compile-command: "gcc -Wall -ggdb -o test test.c"; -*- 
// test.c 

#include "stdio.h" 
#include "stdlib.h" 

int main(int argc, char** argv) 
{ 
    //should print a fairly random number... 
    printf("Super random number: %d\n", rand()); 

    return 0; 
} 

/ -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so"; -*- 
//my_rand.c 

int rand(void) 
{ 
    return 42; 
} 

compilar ambos archivos, a continuación, ejecute: LD_PRELOAD="./my_rand.so" ./test

Super random number: 42

+0

Debería haber agregado que no pude usar LD_PRELOAD (linux) ni DYLD_INSERT_LIBRARIES (mac os x) para lograr esto. La razón de esto es que el programa que estoy tratando de "piratear" inicia una docena de hilos que luego cargan muchos otros procesos, y mi interés es uno de estos procesos. Por lo tanto, el método LD_PRELOAD no me permite inyectar código en el proceso que estoy realmente interesado, sólo se permite que el código que se inyecta en el proceso creado por la ejecución inicial del programa. Por eso mi pregunta es específico para GDB, que me permite unir mi código en el proceso correcto! – karlphillip

+0

@karlphillip ¡Eso ensancha la trama considerablemente! – zdav

+0

¿Por qué no simplemente 'exportar LD_PRELOAD =" ./ my_rand.so "; prueba'? –

5

Tengo una nueva solución, basada en las nuevas restricciones originales. (No borro mi primera respuesta, ya que otros pueden considerarla útil).

He estado investigando un montón, y creo que funcionaría con un poco más de manipulación.

  1. En su .so cambiar el nombre de la función rand sustitución, por ejemplo my_rand
  2. compilar todo y la carga hasta el BGF
  3. info functions uso para encontrar la dirección de rand en la tabla de símbolos
  4. Uso dlopen continuación dlsym para cargar la función en la memoria y obtener su dirección

    call (int) dlopen("my_rand.so", 1) -> Val-

    call (unsigned int) dlsym(-val-, "my_rand") -> my_rand_addr

  5. -la parcial complicado encontrar el código hexadecimal de una instrucción jumpq 0x*my_rand_addr*
  6. Uso set {int}*rand_addr* = *my_rand_addr* para cambiar la instrucción tabla de símbolos ejecución
  7. Continue: ahora cada vez rand se llama, saltará a my_rand vez

Esto es un poco complicado, y muy rotonda, pero estoy bastante seguro de que funcionaría. Lo único que aún no he logrado es crear el código de instrucción jumpq. Todo hasta ese punto funciona bien.

+0

El problema con el paso 5 es que no podrá encontrar saltos a * my_rand_addr * ya que este código se acaba de cargar y la aplicación no lo usa. Pero probé el siguiente http://stackoverflow.com/questions/3270281/can-gdb-make-a-function-pointer-point-to-another-location/3276887#3276887 y ha funcionado. – karlphillip

+0

@karlphillip No necesita encontrar las instrucciones de salto en el código, lo hace usted mismo. El formato de la instrucción 'jumpq' está bien especificado, simplemente búscalo y descubre cuál es el valor hexadecimal. – zdav

0

Uso frecuentemente la inyección de código como método de burla para la prueba automática del código C. Si ese es el tipo de situación en la que te encuentras, si tu uso de GDB es simplemente porque no estás interesado en los procesos principales, y no porque quieras seleccionar interactivamente los procesos que te interesan, entonces aún puedes use LD_PRELOAD para lograr su solución. Su código inyectado solo necesita determinar si está en los procesos padre o hijo. Hay varias maneras de hacerlo, pero en Linux, dado que su hijo procesa exec(), lo más simple es probablemente mirar la imagen ejecutable activa.

Produje dos ejecutables, uno llamado a y el otro b. El ejecutable a imprime el resultado de llamar al rand() dos veces, luego a fork() sy exec() s b dos veces. El ejecutable b imprime el resultado de llamar al rand() una vez. Yo uso LD_PRELOAD para inyectar el resultado de compilar el código siguiente en los archivos ejecutables:

// -*- compile-command: "gcc -D_GNU_SOURCE=1 -Wall -std=gnu99 -O2 -pipe -fPIC -shared -o inject.so inject.c"; -*- 
#include <sys/types.h> 
#include <unistd.h> 
#include <limits.h> 
#include <stdio.h> 
#include <dlfcn.h> 

#define constructor __attribute__((__constructor__)) 

typedef int (*rand_t)(void); 

typedef enum { 
    UNKNOWN, 
    PARENT, 
    CHILD 
} state_t; 

state_t state = UNKNOWN; 
rand_t rand__ = NULL; 

state_t 
determine_state(void) 
{ 
    pid_t pid = getpid(); 
    char linkpath[PATH_MAX] = { 0, }; 
    char exepath[PATH_MAX] = { 0, }; 
    ssize_t exesz = 0; 

    snprintf(linkpath, PATH_MAX, "/proc/%d/exe", pid); 
    exesz = readlink(linkpath, exepath, PATH_MAX); 
    if (exesz < 0) 
     return UNKNOWN; 

    switch (exepath[exesz - 1]) { 
    case 'a': 
     return PARENT; 
    case 'b': 
     return CHILD; 
    } 

    return UNKNOWN; 
} 

int 
rand(void) 
{ 
    if (state == CHILD) 
     return 47; 
    return rand__(); 
} 

constructor static void 
inject_init(void) 
{ 
    rand__ = dlsym(RTLD_NEXT, "rand"); 
    state = determine_state(); 
} 

El resultado de ejecutar a con y sin inyección:

$ ./a 
a: 644034683 
a: 2011954203 
b: 375870504 
b: 1222326746 
$ LD_PRELOAD=$PWD/inject.so ./a 
a: 1023059566 
a: 986551064 
b: 47 
b: 47 

Voy a publicar una solución orientada IAE-tarde .

+0

¡Gracias! Pero como dije anteriormente sobre la pregunta, no puedo usar LD_PRELOAD. – karlphillip

+0

@karlphillip ¡Oh! Pensé que simplemente no creías que 'LD_PRELOAD' pudiera proporcionar el efecto que intentabas lograr, o al menos así es como leo tus comentarios relacionados con' LD_PRELOAD'. Voy a publicar una solución (en mi humilde opinión) limpia basada en 'gdb' pronto. – llasram

+0

Es necesario #define __USE_GNU antes de incluir dlfcn.h si desea utilizar RTLD_NEXT (al menos en mi versión de dlfcn.h). Además, determine_state() no se ejecuta en el horario fork(), por lo que el estado nunca cambiará. – jdizzle

1

para ejecutables podrá encontrar fácilmente la dirección donde el puntero de función se almacena utilizando objdump. Por ejemplo:

objdump -R /bin/bash | grep write 
00000000006db558 R_X86_64_JUMP_SLOT fwrite 
00000000006db5a0 R_X86_64_JUMP_SLOT write 

Por lo tanto, 0x6db5a0 es la dirección del puntero para write. Si lo cambia, las llamadas a escribir se redirigirán a la función elegida. La carga de nuevas bibliotecas en gdb y la obtención de indicadores de función se ha cubierto en publicaciones anteriores. El ejecutable y cada biblioteca tienen sus propios indicadores. La sustitución afecta solo al módulo cuyo puntero fue cambiado.

Para las bibliotecas, es necesario encontrar la dirección base de la biblioteca y añadirlo a la dirección dada por objdump. En Linux, /proc/<pid>/maps lo da. No sé si funcionarían los ejecutables independientes de posición con aleatorización de direcciones. maps -información podría no estar disponible en tales casos.

1

Mientras la función que desea reemplazar está en una biblioteca compartida, puede redirigir las llamadas a esa función en tiempo de ejecución (durante la depuración) por hurgando en el PLT. Aquí está un artículo que podría ser útil:

Shared library call redirection using ELF PLT infection

Está escrito desde el punto de vista de malware modificar un programa, sino un procedimiento mucho más fácil es adaptable a vivir su uso en el depurador. Básicamente, solo necesita encontrar la entrada de la función en el PLT y sobrescribir la dirección con la dirección de la función con la que desea reemplazarla.

Google para "PLT" junto con términos como "ELF", "biblioteca compartida", "vinculación dinámica", "PIC", etc. pueden encontrar más detalles sobre el tema.

+0

No creo que esto funcione en Mac OS X, ¡pero gracias de todos modos! – karlphillip

+0

¿Utiliza OSX alguna forma extraña de vinculación dinámica? Casi todos los demás usan ELF ... –

+0

Sí, debido a su herencia Mach y NeXTSTEP, OS X usa Mach-O. – llasram

2

Varias de las respuestas aquí y el code injection article se enlazó en sus trozos de la cubierta de respuesta de lo que considero la solución óptima gdb -oriented, pero ninguno de ellos tirar todos juntos o cubrir todos los puntos. El código-expresión de la solución es un poco largo, así que aquí está un resumen de los pasos importantes:

  1. carga el código para inyectar. La mayoría de las respuestas publicadas aquí utilizan lo que considero el mejor enfoque: llame al dlopen() en el proceso inferior para vincularlo en una biblioteca compartida que contenga el código insertado.En el artículo que enlazó con el autor, en su lugar, cargó un archivo de objeto reubicable y lo vinculó manualmente con el inferior. Esto es francamente loco: los objetos reubicables no están "listos para funcionar" e incluyen reubicaciones incluso para referencias internas. Y la vinculación manual es tediosa y propensa a errores, mucho más sencilla para que el enlazador dinámico de tiempo de ejecución real haga el trabajo. Esto significa, en primer lugar, obtener libdl en el proceso, pero hay muchas opciones para hacerlo.
  2. Crear un desvío. La mayoría de las respuestas publicadas aquí hasta ahora han involucrado localizar la entrada PLT para la función de interés, usar eso para encontrar la entrada GOT correspondiente, luego modificar la entrada GOT para apuntar a la función inyectada. Esto está bien hasta cierto punto, pero ciertas funciones del enlazador, por ejemplo, el uso de dlsym, pueden eludir el GOT y proporcionar acceso directo a la función de interés. La única manera de estar seguro de interceptar todas las llamadas a una función en particular es sobrescribir las instrucciones iniciales del código de esa función en la memoria para crear una ejecución de "desvío" de redireccionamiento a la función inyectada.
  3. Crear un trampolín (opcional). Con frecuencia, al hacer este tipo de inyección, querrá llamar a la función original cuya invocación está interceptando. La forma de permitir esto con un desvío de funciones es crear un pequeño código "trampolín" que incluye las instrucciones sobrescritas de la función original y luego un salto al resto del original. Esto puede ser complejo, porque las instrucciones relativas a IP en el conjunto copiado deben modificarse para dar cuenta de sus nuevas direcciones.
  4. Automatizar todo. Estos pasos pueden ser tediosos, incluso si se hacen algunas de las soluciones más simples publicadas en otras respuestas. La mejor manera de garantizar que los pasos se realicen correctamente cada vez con parámetros variables (inyectando diferentes funciones, etc.) es automatizar su ejecución. Comenzando con la serie 7.0, gdb ha incluido la capacidad de escribir nuevos comandos en Python. Este soporte se puede usar para implementar una solución llave en mano para inyectar y desviar código en/para el proceso inferior.

Aquí hay un ejemplo. Tengo las mismas a y b ejecutables como antes y un inject2.so creado a partir de la siguiente código:

#include <unistd.h> 
#include <stdio.h> 

int (*rand__)(void) = NULL; 

int 
rand(void) 
{ 
    int result = rand__(); 
    printf("rand invoked! result = %d\n", result); 
    return result % 47; 
} 

A continuación, puedo colocar mis órdenes Python detour en detour.py y tienen el siguiente gdb sesión:

(gdb) source detour.py 
(gdb) exec-file a 
(gdb) set follow-fork-mode child 
(gdb) catch exec 
Catchpoint 1 (exec) 
(gdb) run 
Starting program: /home/llasram/ws/detour/a 
a: 1933263113 
a: 831502921 
[New process 8500] 
b: 918844931 
process 8500 is executing new program: /home/llasram/ws/detour/b 
[Switching to process 8500] 

Catchpoint 1 (exec'd /home/llasram/ws/detour/b), 0x00007ffff7ddfaf0 in _start() 
    from /lib64/ld-linux-x86-64.so.2 
(gdb) break main 
Breakpoint 2 at 0x4005d0: file b.c, line 7. 
(gdb) cont 
Continuing. 

Breakpoint 2, main (argc=1, argv=0x7fffffffdd68) at b.c:7 
7  { 
(gdb) detour libc.so.6:rand inject2.so:rand inject2.so:rand__ 
(gdb) cont 
Continuing. 
rand invoked! result = 392103444 
b: 22 

Program exited normally.

En el proceso secundario, creo un desvío desde la función rand() en libc.so.6 a la función rand() en inject2.so y guardo un puntero a un trampolín para el original rand() en la variable rand__ de inject2.so. Y como se esperaba, el código inyectado llama al original, muestra el resultado completo y devuelve el resultado del módulo 47.

Debido a la longitud, solo estoy enlazando a un pastie que contiene the code for my detour command. Esta es una implementación bastante superficial (especialmente en términos de generación de trampolín), pero debería funcionar bien en un gran porcentaje de casos. Lo he probado con gdb 7.2 (versión más reciente) en Linux con ejecutables de 32 y 64 bits. No lo he probado en OS X, pero cualquier diferencia debería ser relativamente menor.

1

Aún nos puede LD_PRELOAD si hace que la función precargada comprenda las situaciones en las que se está utilizando.He aquí un ejemplo que utilizará la rand() con normalidad, excepto dentro de un proceso bifurcado cuando siempre devolverá 42. Uso las rutinas dl para cargar la función de la biblioteca estándar rand() en un puntero de función para su uso por el rand() secuestrado.

// -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so -ldl"; -*- 
//my_rand.c 
#include <sys/types.h> 
#include <unistd.h> 

#include <dlfcn.h> 


int pid = 0; 
int (*real_rand)(void) = NULL; 

void f(void) __attribute__ ((constructor)); 

void f(void) { 
    pid = getpid(); 
    void* dl = dlopen("libc.so.6", RTLD_LAZY); 
    if(dl) { 
     real_rand = dlsym(dl, "rand"); 
    } 
} 

int rand(void) 
{ 
    if(pid == getpid() && real_rand) 
     return real_rand(); 
    else 
     return 42; 
} 

//test.c 
#include <dlfcn.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
int main(int argc, char** argv) 
{ 

    printf("Super random number: %d\n", rand()); 
    if(fork()) { 
     printf("original process rand: %d\n", rand()); 

    } else { 
     printf("forked process rand: %d\n", rand()); 
    } 

    return 0; 
} 

[email protected]:~$ ./test 
Super random number: 1804289383 
original process rand: 846930886 
forked process rand: 846930886 

[email protected]:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test 
Super random number: 1804289383 
original process rand: 846930886 
forked process rand: 42 
+0

¿Se dio cuenta que ya tenía una respuesta que describe la misma técnica http://stackoverflow.com/questions/3270281/can-gdb-make-a-function-pointer-point-to-another-location/3708318#3708318? – llasram

+0

Su ejemplo no se compilará como se publicó y no funciona. El mío también es más simple. – jdizzle

+0

Esas son razones para mejorar mi respuesta o proponer mejoras, no crear una respuesta completamente diferente y votar por el mío. – llasram

Cuestiones relacionadas