nm – Listado de funciones de un objeto
Si queremos obtener un listado de funciones que tiene un código objeto o una librería compartida se utiliza «nm», que lo descubrí de casualidad.
Este comando lista todos los símbolos que contiene el código objeto o la librería dinámica.
Por ejemplo vamos a crear una librería dinámica con solo una función llamada «factorial».
Este es el código:
#include <stdio.h> int factorial(int n) { return n == 0 ? 1 : n * factorial(n-1); }
Lo compilo así:
gcc factorial.c -o libfactorial.so -shared -fPIC
Ahora analizamos la librería con el comando «nm»:
nm -D libfactorial.so
Obtenemos esto:
w _Jv_RegisterClasses 00002010 A __bss_start w __cxa_finalize w __gmon_start__ 00002010 A _edata 00002018 A _end 000004c8 T _fini 00000314 T _init 0000044c T factorial
Vemos que nuestro función en código maquina esta en el offset del archivo 0x44c.
Si leemos el man, obtenemos el significado de los símbolos:
-
Symbol Type Description A The symbol’s value is absolute, and will not be changed by further linking. B Un-initialized data section D Initialized data section T Normal code section U Undefined symbol used but not defined. Dependency on another library. W Doubly defined symbol. If found, allow definition in another library to resolve dependency.
Las funciones T son las linkables, exceptuando «_fini» y «_init» que son funciones comunes a cualquier librería dinámica. (Esto es una suposición mía, aunque muy lógica por otra parte).
Un ejemplo de código de linkado típico:
#include <stdio.h> int factorial(int n); int main() { printf("5! = %d\n", factorial(5)); return 0; }
Se compila así:
gcc link.c -o link -L. -lfactorial
Y como la librería no esta en una ruta estándar, se ejecutaría así:
export LD_LIBRARY_PATH="." && ./link 5! = 120
Vamos a probar un código que linke dinamicamente, pero en lugar de el fácil «-lname», lo haré con el dload, que nos sirve de ejercicio para entender como funcionan las librerías dinámicas. El código es el siguiente:
#include <stdio.h> // para fprintf #include <stdlib.h> // para exit #include <dlfcn.h> // para dlopen, dlsym, dlerror, dlclose int factorial(int n); int main(int argc, char **argv) { void *lib_handle; int (*factorial)(int); int x; char *error; lib_handle = dlopen("libfactorial.so", RTLD_LAZY); if (!lib_handle) { fprintf(stderr, "%s\n", dlerror()); exit(1); } factorial = dlsym(lib_handle, "factorial"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(1); } x = factorial(5); printf("5! = %d\n",x); dlclose(lib_handle); return 0; }
El código hay que mirarlo detenidamente, especialmente el puntero a función, pero por lo demás, es sencillo y se autocomenta.
Se compila así:
gcc -rdynamic -o dlopen dlopen.c -ldl
Probamos el ejecutable:
export LD_LIBRARY_PATH="." && ./dlopen 5! = 120objdump – Desensamblando de binarios
Con este comando podemos obtener el código en ensamblador a partir del código maquina. El comando es el siguiente:
objdump -d libfactorial.so
Si además lo compilamos introduciendo los símbolos de debugeo (parámetro -g), podemos ver el código en ensamblador combinado con el código fuente. Se añadiría el parametro -S:
objdump -d -S libfactorial.so
Ya sabíamos que nuestra función «factorial» estaba en el offset 0x44c, por tanto lo busco en la salida de objdump y pasteo la salida (en este caso, con símbolos de debugeo):
0000044c <factorial>: #include <stdio.h> int factorial(int n) { 44c: 55 push %ebp 44d: 89 e5 mov %esp,%ebp 44f: 53 push %ebx 450: 83 ec 14 sub $0x14,%esp 453: e8 ef ff ff ff call 447 <__i686.get_pc_thunk.bx> 458: 81 c3 9c 1b 00 00 add $0x1b9c,%ebx return n == 0 ? 1 : n * factorial(n-1); 45e: 83 7d 08 00 cmpl $0x0,0x8(%ebp) 462: 74 14 je 478 <factorial+0x2c> 464: 8b 45 08 mov 0x8(%ebp),%eax 467: 83 e8 01 sub $0x1,%eax 46a: 89 04 24 mov %eax,(%esp) 46d: e8 f2 fe ff ff call 364 <factorial@plt> 472: 0f af 45 08 imul 0x8(%ebp),%eax 476: eb 05 jmp 47d <factorial+0x31> 478: b8 01 00 00 00 mov $0x1,%eax } 47d: 83 c4 14 add $0x14,%esp 480: 5b pop %ebx 481: 5d pop %ebp 482: c3 retCon el poco ensamblador que me enseñan en la uni, puedo deducir los siguientes offsets:
45e: Es la comparación de n con 0
462: Si la igualdad anterior se cumple, salta a la línea 44c + 2c = 478
478: Es el interior del IF, iguala el parámetro que viene de la pila con 1. Definiendo 0! = 1
464: Se ejecutaría si la comparación de 45e no se da. Se asigna la vuelta de la recursión
467: Se resta 1 a n
46a: Se prepara e parámetro n-1 en eax, previo a una llamada.
46d: Se hace la llamada recursiva a si mismo.
472: Se multiplica por 8 la vuelta de la recursión.
476: Estamos en el else, por tanto esta jmp se salta el interior del if. 44c + 31 = 47d
47d: De aquí en adelante prepara el return, deja la pila como estaba antes de la llamada y se restaura el IP.Vamos a hacer algo fácil «crackear»(si se le puede llamar crackeo a esto xD) el programa para cambiar la asignación de:
0!=1 por 0!=4Por tanto el 5! en lugar de dar 120 va dar 4 veces más, 480.
Para hello abrimos con algún editor de hexadecimal:
ghex2 libfactorial.soVamos al OFFSET 479 (478 + 1) y cambiamos el 01 por 04.
Hacemos la prueba de fuego:
export LD_LIBRARY_PATH="." && ./dlopenY efectivamente:
5! = 480OuYeah!
Bueno con esto acabo, este articulo es sobre todo didáctico por lo que si alguno puede completar más el articulo con sus comentarios, se le agradeceremos todos.
Fuentes:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://unixhelp.ed.ac.uk/CGI/man-cgi?nm
http://node1.yo-linux.com/cgi-bin/man2html?cgi_command=ld
http://node1.yo-linux.com/cgi-bin/man2html?cgi_command=ldconfigFiled under: compiladores, cracking, geek, hacking, Linux, programación, Tips | Tagged: compiladores, cracking, dlopen, librerías dinámicas, libs, nm, objdump, shared |
Me encanto el tutorial, he aprendido bastante.
Añado una cosa, que acabo de aprender.
Si tenemos librerías estáticas libXXX.a
Estas librerías son realmente contenedores de código objeto, es decir, muchos archivos xxxx.cpp.o
Para extraerlos es:
ar x libXXX.a
Y los extrae junto a la librería.