punteros puntero funciones ejemplos declaracion con cadenas arreglos aritmetica c macos gdb

funciones - ¿Puede gdb hacer que un puntero de función apunte a otra ubicación?



punteros en c pdf (10)

Con frecuencia utilizo la inyección de código como un método de burla para las pruebas automatizadas de código C. Si ese es el tipo de situación en la que se encuentra, si su uso de GDB es simplemente porque no está interesado en los procesos principales, y no porque quiera seleccionar interactivamente los procesos que son de interés, entonces aún puede Usa LD_PRELOAD para lograr tu solución. Su código inyectado solo necesita determinar si está en los procesos padre o hijo. Hay varias formas de hacerlo, pero en Linux, dado que su hijo procesa exec() , la más sencilla es probablemente mirar la imagen ejecutable activa.

Produje dos ejecutables, uno llamado a y el otro b . El archivo ejecutable a imprime el resultado de llamar a rand() dos veces, luego fork() sy exec() s b dos veces. Ejecutable b imprime el resultado de llamar a rand() una vez. Uso LD_PRELOAD para inyectar el resultado de compilar el siguiente código en los 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

Publicaré una solución orientada a gdb más tarde.

Lo explicaré:

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

Entonces adjunto gdb a este proceso y hago que cargue mi biblioteca compartida personalizada (que tiene una función rand() personalizada):

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

Esto colocaría la función rand() personalizada dentro de la memoria del proceso. Sin embargo, en este punto, el símbolo rand seguirá apuntando a la función rand() predeterminada. ¿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 tengo permiso para usar los LD_PRELOAD (linux) ni DYLD_INSERT_LIBRARIES (mac os x) para esto, porque 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 subprocesos y algunos de ellos inician nuevos procesos, y estoy interesado en 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.



Esta pregunta me intrigó, así que hice una pequeña investigación. Lo que estás buscando es una '' inyección dll ''. Usted escribe una función para reemplazar alguna función de biblioteca, la pone en un .so y le dice a ld que cargue su dll. Acabo de probarlo 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 solo para gdb, vea mi otra solución.

// -*- 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; }

compile ambos archivos, luego ejecute: LD_PRELOAD="./my_rand.so" ./test

Super random number: 42


No estoy seguro de cómo hacer esto en un programa en ejecución, pero quizás LD_PRELOAD funcione para usted. Si configura 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 otras.

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

Debe hacer esto antes de iniciar el proceso, pero no tiene que reconstruir el programa.


Para los ejecutables, puede encontrar fácilmente la dirección donde se almacena el puntero de función 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 punteros a funciones se ha cubierto en publicaciones anteriores. El ejecutable y cada biblioteca tienen sus propios punteros. El reemplazo afecta solo al módulo cuyo puntero fue cambiado.

Para las bibliotecas, necesita encontrar la dirección base de la biblioteca y agregarla a la dirección dada por objdump. En Linux, /proc/<pid>/maps da a conocer. No sé si funcionarán los ejecutables independientes de la posición con asignación aleatoria de direcciones. maps -la información podría no estar disponible en tales casos.


Seguí esta publicación y esta presentación y se me ocurrió el siguiente conjunto de comandos gdb para OSX con ejecutable x86-64, que se puede cargar con la opción -x cuando se adjunta al 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 está en el set $p = ... comando. dyld_stub_rand es una instrucción de salto de 6 bytes. El offset de salto está en dyld_stub_rand+2 (4 bytes). Este es un salto relativo a $rip , así que agregue una compensación a lo que $rip sería en este punto (justo después de la instrucción, dyld_stub_rand+6 ).

Esto apunta a una entrada de la tabla de símbolos, que debe ser una rutina de enrutador dinámico o rand real para cargarla (si nunca se llamó). Luego es reemplazado 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.


Siempre que la función que desea reemplazar se encuentre en una biblioteca compartida, puede redirigir las llamadas a esa función en tiempo de ejecución (durante la depuración) pinchando en el PLT. Aquí hay un artículo que podría ser útil:

Redirección de llamadas a la biblioteca compartida mediante la infección por ELF PLT

Está escrito desde el punto de vista del malware que modifica un programa, pero un procedimiento mucho más fácil es adaptable al uso en vivo 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.

Buscar en Google "PLT" junto con términos como "ELF", "biblioteca compartida", "enlace dinámico", "PIC", etc. puede encontrar más detalles sobre el tema.


Tengo una nueva solución, basada en el nuevo Restricciones originales. (No estoy eliminando mi primera respuesta, ya que a otros les puede resultar útil).

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

  1. En su .so cambie el nombre de su función rand de reemplazo, por ejemplo, my_rand
  2. Compila todo y carga gdb
  3. Usa las info functions para encontrar la dirección de rand en la tabla de símbolos
  4. Use dlopen luego 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 parte difícil- encuentra el código hexadecimal de una jumpq 0x*my_rand_addr*
  6. Use set {int}*rand_addr* = *my_rand_addr* para cambiar la instrucción de la tabla de símbolos
  7. Continue ejecución: ahora cada vez que se llama a rand , saltará a my_rand en my_rand lugar

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


Todavía puede LD_PRELOAD si hace que la función precargada comprenda las situaciones en las que se está utilizando. Este es un ejemplo que usará el rand() manera normal, excepto dentro de un proceso de bifurcación cuando siempre devolverá 42. Uso las rutinas dl para cargar la función rand() la biblioteca estándar en un puntero de función para que lo utilice 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; }

jdizzle@pudding:~$ ./test Super random number: 1804289383 original process rand: 846930886 forked process rand: 846930886 jdizzle@pudding:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test Super random number: 1804289383 original process rand: 846930886 forked process rand: 42


Varias de las respuestas aquí y el artículo de inyección de código que vinculó en su respuesta cubren partes de lo que considero la solución orientada a gdb óptima, pero ninguna de ellas reúne todo o cubre todos los puntos. El código de expresión de la solución es un poco largo, así que aquí hay un resumen de los pasos importantes:

  1. Cargue el código a inyectar . La mayoría de las respuestas publicadas aquí usan lo que considero el mejor enfoque: llame a dlopen() en el proceso inferior para enlazar en una biblioteca compartida que contenga el código inyectado. En el artículo que vinculó con el autor, en su lugar, cargó un archivo de objeto reubicable y lo vinculó a mano con el inferior. Esto es francamente insano: los objetos reubicables no están "listos para ejecutarse" e incluyen reubicaciones incluso para referencias internas. Y la vinculación manual es tediosa y propensa a errores, mucho más sencilla de permitir que el enlazador dinámico de tiempo de ejecución real haga el trabajo. Esto significa, en primer lugar, libdl a 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 la ubicación de la entrada PLT para la función de interés, usándola para encontrar la entrada GOT coincidente, y luego modificando la entrada GOT para que apunte a su función inyectada. Esto está bien hasta cierto punto, pero ciertas características del enlazador (por ejemplo, el uso de dlsym ) pueden burlar el GOT y proporcionar acceso directo a la función de interés. La única forma de estar seguro de interceptar todas las llamadas a una función en particular es sobrescribir las instrucciones iniciales del código en memoria de esa función para crear una ejecución de desvío a su función inyectada.
  3. Crear un trampolín (opcional). Con frecuencia, cuando realice 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 la función es crear un pequeño código "trampolín" que incluya las instrucciones sobrescritas de la función original y luego un salto al resto del original. Esto puede ser complejo, porque cualquier instrucción relativa al IP en el conjunto copiado debe modificarse para tener en cuenta sus nuevas direcciones.
  4. Automatiza 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 asegurarse de que los pasos se realicen correctamente cada vez con parámetros variables (inyectar diferentes funciones, etc.) es automatizar su ejecución. A partir de la serie 7.0, gdb ha incluido la capacidad de escribir nuevos comandos en Python. Este soporte puede usarse para implementar una solución llave en mano para inyectar y desviar código en / al proceso inferior.

Aquí hay un ejemplo. Tengo los mismos ejecutables b que antes y un inject2.so creado a partir del 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; }

Luego puedo colocar mi comando de detour Python en detour.py y tener la siguiente sesión de gdb :

(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 hijo, creo un desvío de la función rand() en libc.so.6 a la función rand() en inject2.so y inject2.so un puntero a un trampolín para el rand() original rand() en la variable inject2.so de inject2.so . Y como se esperaba, el código inyectado llama al original, muestra el resultado completo y devuelve ese resultado módulo 47.

Debido a la longitud, solo me estoy vinculando a un pastel que contiene el código de mi comando de detour . Esta es una implementación bastante superficial (especialmente en términos de la generación de trampolines), pero debería funcionar bien en un gran porcentaje de casos. Lo he probado con gdb 7.2 (la versión más reciente) en Linux con ejecutables tanto de 32 bits como de 64 bits. No lo he probado en OS X, pero cualquier diferencia debería ser relativamente menor.