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 - 0x55bd0a0ac670

On 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

  1. We can see that the username pointer is pointing to 0x603460 - 0x73616c6f6863696e - "nicholas" and our input has overridden the authorization level 0x3837363534333231

  2. Since they only compare the last 4 bytes of the authorization level 0x34333231, we can simply adjust our payload to <username>\x05 and send it giving us the flag

Example 2 - Swamp19 Heap Golf

Solve

  1. Allocate 4 buffers of the same size (32), which the fourth buffer will be equivalent to 4

  1. 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

  2. We can allocate 4 more chunks of 32 bytes each, which will eventually lead to chunk4 restoring its old value of 4

Last updated