objdump es un desensamblador por línea de comandos que genera el código en assembler de un archivo ejecutable. Se usan desensambladores para generar el código en assembler y comprender el funcionamiento de un binario. Es decir, es necesario pasar de los ceros y unos almacenados en memoria a su representación simbólica legible para humanos. Aunque de acuerdo al código del binario del que se parta, el código en assembler resultante puede contener errores.
Un programa simple que hace una suma con el código fuente:
int suma(int a, int b) {
return a+b;
}
int main() {
int resultado = suma(1,2);
}
La primer columna son las direcciones dentro del espacio de direcciones del programa. La segunda muestra el código máquina de cada instrucción y la última el código desensamblado del programa en assembler.
GDB
gdb es un debugger por línea de comandos que permite ejecutar un programa con “puntos de ruptura” o breakpoints para monitorear los contenidos de la memoria y de los registros del procesador en cualquier momento de la ejecución. Permite llevar a cabo el análisis dinámico de un binario para seguir o modificar el flujo de ejecución.
Para debuggear un programa se lo debe compilar con la opción -ggdb de debugging.
user@abos:~$ gcc -m32 -fno-stack-protector -ggdb -mpreferred-stack-boundary=2 -z execstack -o programa programa.c
user@abos:~$ gdb programa
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
...
Reading symbols from secciones...(no debugging symbols found)...done.
>>> break main
Breakpoint 1 at 0x80484c1
>>> run
...
Un recurso muy útil para simplificar la tarea de debugging es usar herramientas como Pwndbg, Dashbord GDB, Voltron u otros similares.
Otro recurso valioso que condensa las directivas más útiles de gdb es esta hoja de referencias y también la guía de Exploit Database.
Incluir input por entrada estándar:
Frecuentemente vamos a necesitar debuggear un programa vulnerable que toma un input por entrada estándar (es decir, por stdin). Por ejemplo el programa stack 1 con gets(buf).
Generamos un archivo con el input y al debuggearlo con gdb es posible procesarlo como un input por stdin de la siguiente manera:
user@u:~$ python input.py > in
user@u:~$ gdb ./stack1
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
...
(gdb) r < in
...
Incluir argumentos:
Si en cambio queremos debuggear un programa recibe un argumento. Por ejemplo el abo 3 con strcpy(buf,argc[1]) y fn(argc[2]) espera dos argumentos:
user@u:~$ gdb ./abo3
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
...
(gdb) r "$(./arg1.py)" "$(./arg2.py)"
...
Desensamblar:
Al igual que con objdump, con gdb también es posible desensamblar un binario, intercalando el código fuente y el assembler con la directiva disas:
user@u:~$ gdb ./abo1
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
...
(gdb) disas /m main
Dump of assembler code for function main:
5 int main() {
0x080483d8 <+0>: push ebp
0x080483d9 <+1>: mov ebp,esp
0x080483db <+3>: sub esp,0x4
6 int resultado = suma(1,2);
0x080483de <+6>: push 0x2
0x080483e0 <+8>: push 0x1
0x080483e2 <+10>: call 0x80483cb
0x080483e7 <+15>: add esp,0x8
0x080483ea <+18>: mov DWORD PTR [ebp-0x4],eax
7 }
0x080483ed <+21>: leave
0x080483ee <+22>: ret
End of assembler dump.
>>>
Simulación de valores:
Con gdb es posible modificar valores de variables durante la ejecución de un programa. Es un recurso muy útil para probar el funcionamiento de un exploit antes de comenzar a construirlo. Por ejemplo, si se quiere modificar el valor de una variable como cookie en el Stack 1 (o de una dirección de retorno ya identificada), es posible testear previamente el funcionamiento de esta estrategia con gdb:
Con strace es posible interceptar las llamadas al sistema que realiza un programa. Por ejemplo, el siguiente programa es posible rastrear como printf() redunda en una llamada write:
test-syscalls.c
#include
int main() {
printf("Hola mundo!\n");
return 0;
}
Y con strace podemos ver la llamada a la syscall write.
Para verificar las tecnicas de mitigación habilitadas en un binario es de utilidad usar el script checksec
user@abos:~$ checksec.sh --file programa
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH programa
SECCIONES DE UN BINARIO
OBJDUMP
objdump permite ver las diferentes secciones de un archivo ejecutable.
#include
int main(){
printf("Inspección de secciones.");
}
File off: offset de la sección desde el principio del archivo.
Algn: Alineación.
Y las flags: CONTENTS, ALLOC, LOAD, READONLY, DATA incluyen más información sobre las secciones.
También con objdump es posible ver el contenido de cada sección.
user@abos:~$ objdump -s test
......
Contents of section .rodata:
80484b8 03000000 01000200 496e7370 65636369 ........Inspecci
80484c8 c36e2064 65207365 6363696f 6e65732e .n de secciones.
80484d8 00
En la sección .rodata encontramos el string que imprime el programa. Y también podemos averiguar las direcciones de las entradas de la tabla GOT del binario.
user@abos:~$ objdump --dynamic-reloc test
test: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
080496b8 R_386_GLOB_DAT __gmon_start__
080496c8 R_386_JUMP_SLOT printf
080496cc R_386_JUMP_SLOT __gmon_start__
080496d0 R_386_JUMP_SLOT __libc_start_main
READELF
También es posible obtener información relacionada a las secciones de un binario con:
user@abos:~$ readelf -S test
There are 30 section headers, starting at offset 0xea0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0804818c 00018c 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481ac 0001ac 000050 10 A 6 1 4
[ 6] .dynstr STRTAB 080481fc 0001fc 00004c 00 A 0 0 1
[ 7] .gnu.version VERSYM 08048248 000248 00000a 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 08048254 000254 000020 00 A 6 1 4
[ 9] .rel.dyn REL 08048274 000274 000008 08 A 5 0 4
[10] .rel.plt REL 0804827c 00027c 000018 08 AI 5 12 4
[11] .init PROGBITS 08048294 000294 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482c0 0002c0 000040 04 AX 0 0 16
[13] .text PROGBITS 08048300 000300 0001a2 00 AX 0 0 16
[14] .fini PROGBITS 080484a4 0004a4 000014 00 AX 0 0 4
[15] .rodata PROGBITS 080484b8 0004b8 000021 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 080484dc 0004dc 00002c 00 A 0 0 4
[17] .eh_frame PROGBITS 08048508 000508 0000bc 00 A 0 0 4
[18] .init_array INIT_ARRAY 080495c4 0005c4 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 080495c8 0005c8 000004 00 WA 0 0 4
[20] .jcr PROGBITS 080495cc 0005cc 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 080495d0 0005d0 0000e8 08 WA 6 0 4
[22] .got PROGBITS 080496b8 0006b8 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 080496bc 0006bc 000018 04 WA 0 0 4
[24] .data PROGBITS 080496d4 0006d4 000008 00 WA 0 0 4
[25] .bss NOBITS 080496dc 0006dc 000004 00 WA 0 0 1
[26] .comment PROGBITS 00000000 0006dc 000039 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 000715 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 00081c 000430 10 29 45 4
[29] .strtab STRTAB 00000000 000c4c 000254 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
OTRAS HERRAMIENTAS
FILE
Imprime el tipo de archivo del que se trata.
STRINGS
GNU strings imprime las secuencias de más de 4 caracteres imprimibles que encuentra dentro de un binario.
HEXDUMP
Muestra el contenido de un binario en hexadecimal.