"Add" allocates C++ objects, and "perform" calls the first method of each object. Once an object is deleted its slot cannot be refilled, and its actions cannot be performed. As shown above, the pond's
action discloses the address of its object, providing us with a free leak.

The vulnerability lies in the custom heap allocation scheme used for those zen objects in the addObject action:

Meanwhile, bkfree() called by deleteObject() simply does chunk->free++, even if the chunk is already unused. DeleteObject() does not check whether the object as been deleted already (wheareas perform does).
This means we can have multiple objects sitting in the same chunk if we follow this exploitation path:

1. create the largest object: gets allocated by sbrk.

2. delete it: the chunk becomes unused.

3. create a new object. As the first one was larger, the new object gets allocated within the first chunk.

4. delete the first object: the new object is still available in the objects list, but its chunk is marked as free.

5. create a new object. As the first one was larger, the new object gets allocated within the first chunk.

6. The two last objects are accessible and still live in the same chunk, which means the third one may overwrite the second's data.

The largest object is rake, and the sign object is just a getline() of arbitrary data, so we can overwrite the vtable pointer of any other objects residing at the same address. Using the address leak
of pond, we can pretty easily control EIP:

While we have an easy EIP control, there isn't anything really interesting on the stack and within registers to pivot the stack. edx and *esp+4 point to our chunk, starting with our fake vtable and function pointer, so we would need a very lucky ret chunk to get stack control in one chunk.

To get stack control, I used the simplePrint function, that performs a sprintf to a stack buffer at 0x08049210:

As we control *esp+4, pointing to our sign's content, this overwrites the stack with arbitrary data. Having the remote libc, it's now just a matter of roping to system() by first disclosing a GOT address to get the actual system()'s libc address. To avoid the pain of forging two
separate rop stacks (one for before the GOT disclosure, one for the system() call), I used a little trick consisting in performing a read within a GOT address and then calling the associated PLT to call an arbitrary adress read from stdin:

Flagz (high five if like me you submitted the flag{} part): Knight turned the machine off and on. The machine worked. There was also another bug: the getline() used for the sign object takes as a parameter
a buffer that isn't a valid malloced chunk. As a result, if getline() needs more bytes than the available space, it triggers a realloc() on an invalid chunk, with the chunk's size under our control (pointed to by chunk->unused that can be incremented at will).
I actually spent much time trying to exploit that by forcing realloc to give back the same address, but couldn't get rid of a random crash within a subcall of realloc() so I don't know if we could do much more with it.