This level explores why you should always explicitly initialize your allocated memory, and what can occur when pointer values go stale.
The description and source code can be found here:
http://exploit.education/phoenix/heap-two/
So I’m actually a little embarrassed about how long it took me to find the solution here. There’s really no need to use GDB if you have an understanding of the C programming language (more specifically, pointers and the allocation/deallocation of memory).
Let’s analyze the source code. First, we have declarations for the auth struct, a pointer to the auth struct, and a character pointer called service. The main() function declares a “string” variable called line and prints the banner:
struct auth { char name[32]; int auth; }; struct auth *auth; char *service; int main(int argc, char **argv) { char line[128]; printf("%s\n", BANNER); ...
In the heart of the program is an infinite while loop. This loop prints the current values of the auth & service pointers, waits for user input, and uses several if statements to give the user a list of commands to run. The possibilities are auth, reset, service, and login:
... while (1) { printf("[ auth = %p, service = %p ]\n", auth, service); if (fgets(line, sizeof(line), stdin) == NULL) break; if (strncmp(line, "auth ", 5) == 0) { auth = malloc(sizeof(struct auth)); memset(auth, 0, sizeof(struct auth)); if (strlen(line + 5) < 31) { strcpy(auth->name, line + 5); } } if (strncmp(line, "reset", 5) == 0) { free(auth); } if (strncmp(line, "service", 6) == 0) { service = strdup(line + 7); } if (strncmp(line, "login", 5) == 0) { if (auth && auth->auth) { printf("you have logged in already!\n"); } else { printf("please enter your password\n"); } } } }
If the first 5 characters of user input equals "auth ", then several other things happen. Memory is allocated on the heap for the auth struct, it’s initialized with null bytes, and if the length of user input after the command is less than 31, then it’s saved to the struct’s name variable.
If the first 5 characters of user input equals "reset", then the heap space memory for the auth struct is freed.
If the first 6 characters of user input equals "servic" (I’m not sure why this doesn’t include the last letter of “service”, perhaps a typo?), then all user input after the first 7 characters is saved to the heap with the service variable pointing to it.
If the first 5 characters of user input equals "login", then an additional check occurs. If the auth struct is defined as well as the auth->auth member (integer variable), then we complete the level with the message, “you have logged in already!” Otherwise, we’ll get the message, “please enter your password.”
We can actually use these commands, one at a time and in order, to solve this level. But first, it’s important to understand the layout of memory in the heap for these variables. When memory is allocated for the auth struct, it will reserve 36 bytes on the heap. 32 for the auth->name “string” and 4 for the auth->auth integer. The program doesn’t allow us to directly modify the integer (and a good thing too or else this wouldn’t be a challenge), but if it did, this would be saved right after the 32 bytes for the name variable. When the free() function is used (with a pointer to that space as an argument), those 36 bytes on the heap are now available for use. In this case, the next time malloc() or strdup() is called, those bytes will be re-used.
With that in mind, I present the solution. First, I get the auth struct initialized with the “auth” command using any ‘ol value (in this case, a single ‘A’). Next, I free the heap space using the “reset” command. Note, however, that this does not remove, delete, or change the auth pointer in any way. This is now what’s called a “stale pointer.” Then I’ll use the “service” command to save any ‘ol value to the heap that’s at least 32 bytes long (in this case, 32 ‘B’s). This will write data into the space that was once used for the auth->auth integer. If, exactly, 32 bytes is used, then that space will hold the newline character (0x0a) that’s saved at the end of the user input. Finally, I’ll use the “login” command to verify that this worked:
user@phoenix-amd64:~$ /opt/phoenix/i486/heap-two Welcome to phoenix/heap-two, brought to you by https://exploit.education [ auth = 0, service = 0 ] auth A [ auth = 0x8049af0, service = 0 ] reset [ auth = 0x8049af0, service = 0 ] serviceBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB [ auth = 0x8049af0, service = 0x8049af0 ] login you have logged in already!
I also wanted to provide a bit of a visual with what’s happening in the heap:
Heap space after "auth A" command: ADDRESS VALUE +------------+ 0x08049af0 | 0x00000a41 | <= auth->name variable (char) 0x08049af4 | 0x00000000 | 0x08049af8 | 0x00000000 | 0x08049afc | 0x00000000 | 0x08049b00 | 0x00000000 | 0x08049b04 | 0x00000000 | 0x08049b08 | 0x00000000 | 0x08049b0c | 0x00000000 | 0x08049b10 | 0x00000000 | <= auth->auth variable (int) +------------+ Heap space after "service" + 32x'B' command: ADDRESS VALUE +------------+ 0x08049af0 | 0x42424242 | <= auth->name variable (char) 0x08049af4 | 0x42424242 | 0x08049af8 | 0x42424242 | 0x08049afc | 0x42424242 | 0x08049b00 | 0x42424242 | 0x08049b04 | 0x42424242 | 0x08049b08 | 0x42424242 | 0x08049b0c | 0x42424242 | 0x08049b10 | 0x0000000a | <= auth->auth variable (int) +------------+
As you can see, that last newline character (0x0a) has now changed the former auth->auth variable. This program demonstrates what’s called a Use After Free vulnerability This happens when data on the heap is freed, but a leftover reference or “stale pointer” is used by the code as if the data were still valid.
Hey man how did you figure out what the heap looked like, when I try to “info proc mappings” from gdb [heap] doesn’t display?
If I remember correctly, I just found the pointer to these variables on the stack. Also, if you’re using GEF (https://github.com/hugsy/gef), you can use the “vmmap” command. That should show you the addresses for the heap.
Ahh duh, thanks man!
Hey man, love your article. But there is one thing I don’t understand thoroughly. When I tried to input “service” + an number of characters that less than 32 character after reset auth, service always point to an address that different with address of auth. I presumed that the algorithm of malloc() is the reason.
So my question is that why we have to input “service” + at least 32 characters to make it point to the used to be address of auth?
Did you do the “reset” command? That’s what frees the heap space. After that, the “service” command will re-use that same heap space and allow you to overwrite the location that “auth->auth” used to point to.