16 April 2019

Heap 2

Write-up for: https://exploit.education/phoenix/heap-two/.

The vulnerability

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 auth command is issued again, new memory is allocated and assigned to the auth pointer variable. Issuing the reset command will free the memory pointed to by the auth pointer.

There is a memory leak issue in the program. If the auth command is given multiple times without a corresponding reset call, 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 NULL. 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, but 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 ]

First, auth and service are initialized to NULL. 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 :).

Use-after-free

Stale pointer