Heap Grooming
Manipulating the heap in a certain way
First Fit
Freeing a memory in prorgram, different bins have different ways they approach re-allocation
Fast Bins
Since Fast Bins follow LIFO (Last In First Out), the allocator will return the most recently freed chunk from the head of the fastbin (considering that the size is the same)
// Example
char *a = malloc(20); // 0x55bd0a0ac670
char *b = malloc(20); // 0x55bd0a0ac690
char *c = malloc(20); // 0x55bd0a0ac6b0
free(a);
free(b);
free(c);
b = malloc(20); // c - 0x55bd0a0ac6b0
c = malloc(20); // b - 0x55bd0a0ac690
d = malloc(20); // a - 0x55bd0a0ac670On older versions of libc, malloc does not clear the memory that has been freed, we can use this to exploit the fact that the new allocation and freed size are the same.
Example 1 - PicoCTF AreYouRoot
int main(int argc, char **argv){
char buf[512];
char *arg;
uint32_t level;
struct user *user;
setbuf(stdout, NULL);
menu();
user = NULL;
while(1){
puts("\nEnter your command:");
putchar('>'); putchar(' ');
if(fgets(buf, 512, stdin) == NULL)
break;
if (!strncmp(buf, "show", 4)){
if(user == NULL){
puts("Not logged in.");
}else{
printf("Logged in as %s [%u]\n", user->name, user->level);
}
}else if (!strncmp(buf, "login", 5)){
if (user != NULL){
puts("Already logged in. Reset first.");
continue;
}
arg = strtok(&buf[6], "\n");
if (arg == NULL){
puts("Invalid command");
continue;
}
user = (struct user *)malloc(sizeof(struct user));
if (user == NULL) {
puts("malloc() returned NULL. Out of Memory\n");
exit(-1);
}
user->name = strdup(arg);
printf("Logged in as \"%s\"\n", arg);
}else if(!strncmp(buf, "set-auth", 8)){
if(user == NULL){
puts("Login first.");
continue;
}
arg = strtok(&buf[9], "\n");
if (arg == NULL){
puts("Invalid command");
continue;
}
level = strtoul(arg, NULL, 10);
if (level >= 5){
puts("Can only set authorization level below 5");
continue;
}
user->level = level;
printf("Set authorization level to \"%u\"\n", level);
}else if(!strncmp(buf, "get-flag", 8)){
if (user == NULL){
puts("Login first!");
continue;
}
if (user->level != 5){
puts("Must have authorization level 5.");
continue;
}
give_flag();
}else if(!strncmp(buf, "reset", 5)){
if (user == NULL){
puts("Not logged in!");
continue;
}
free(user->name);
user = NULL;
puts("Logged out!");
}else if(!strncmp(buf, "quit", 4)){
return 0;
}else{
puts("Invalid option");
menu();
}
}
}
// User Structure
struct user {
char *username;
double authorization_level;
}Solve
We need authorization level of 5 to get the flag but we can only set it to 4 using the legitimate method
The chances of the allocator reallocating the user buffer after freeing is high as the user struct is not freed upon reset
We can just send a specially crafted username to see if the user struct will be reused upon reset
We can see that the username pointer is pointing to
0x603460 - 0x73616c6f6863696e - "nicholas"and our input has overridden the authorization level0x3837363534333231Since they only compare the last 4 bytes of the authorization level
0x34333231, we can simply adjust our payload to<username>\x05and send it giving us the flag
Example 2 - Swamp19 Heap Golf
Solve
Allocate 4 buffers of the same size (32), which the fourth buffer will be equivalent to 4
When the chunks are freed, they are freed from index 0 to 4 and the another malloc(32) is called -> pointing to the address of the chunk 4
We can allocate 4 more chunks of 32 bytes each, which will eventually lead to chunk4 restoring its old value of 4
Last updated