Write-up for: https://exploit.education/phoenix/heap-two/.
The goal of this challenge is trick the program into thinking that the user is authenticated. This requires two preconditions to be true:
auth && auth->auth
auth needs to be a valid pointer and the value of
auth->auth needs to be non-zero.
auth can be set by entering the string
auth. This leads to allocation of memory on
the heap for the
auth struct. The memory is zero-initialized and the string after the
auth command is copied to the
name field (length-checked). Every time, the
is issued again, new memory is allocated and assigned to the
auth pointer variable.
reset command will free the memory pointed to by the
There is a memory leak issue in the program. If the
auth command is given multiple
times without a corresponding
the allocated memory is leaked since the program has no way of freeing the memory.
However, the security issue here is more subtle. The
auth pointer is never set to
This means, even if the memory pointed to by
auth is freed, it still points to the memory.
Thus, the check if
auth is not
NULL will succeed. Then, the
auth pointer is dereferenced
to access the
auth->auth field. This is undefined behavior. Since this memory was freed,
it can be re-used by subsequent allocations.
When we issue the
service command, the
strdup function will also allocate memory. In
the used malloc implementation, the memory previously pointed to by
auth will be reused,
auth still points there. This way, via the
service pointer, we can manipulate the
heap in a way that tricks the program in thinking
auth->auth != 0. Let’s see this in action:
user@phoenix-amd64:/opt/phoenix/amd64$ ./heap-two Welcome to phoenix/heap-two, brought to you by https://exploit.education [ auth = 0, service = 0 ] auth AAA [ auth = 0x600e40, service = 0 ] reset [ auth = 0x600e40, service = 0 ] service A [ auth = 0x600e40, service = 0x600e40 ] login you have logged in already! [ auth = 0x600e40, service = 0x600e40 ]
service are initialized to
auth AAA allocates memory at
0x600e40 which is
freed by the
reset call. However,
auth still points to the memory. This memory is re-used in the subsequent
service A call. As you can see,
service points to the same memory location!
The final call to
login will now follow the stale
auth pointer and we see the success message. Done!
We could confirm all this in GDB but since the program provides nice output we’ll skip this exercise this time :).