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.
Encontré este tutorial increíblemente útil, y hasta ahora es la única forma en que logré lo que buscaba con GDB: Inyección de código en la aplicación de Linux en ejecución : http://www.codeproject.com/KB/DLL/code_injection.aspx
También hay una buena sesión de preguntas y respuestas sobre el código de inyección para Mac aquí: http://www.mikeash.com/pyblog/friday-qa-2009-01-30-code-injection.html
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.
- En su .so cambie el nombre de su función rand de reemplazo, por ejemplo, my_rand
- Compila todo y carga gdb
- Usa las
info functions
para encontrar la dirección derand
en la tabla de símbolos 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- -la parte difícil- encuentra el código hexadecimal de una
jumpq 0x*my_rand_addr*
- Use
set {int}*rand_addr* = *my_rand_addr*
para cambiar la instrucción de la tabla de símbolos -
Continue
ejecución: ahora cada vez que se llama arand
, saltará amy_rand
enmy_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:
- 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
alibdl
en el proceso, pero hay muchas opciones para hacerlo. - 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. - 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.
- 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.