Protostar Heap 2

protostar heap2

this will be the explanation on the protostart heap2 Use After Free vulnerability

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

struct auth {
  char name[32];
  int auth;
};

struct auth *auth;
char *service;

int main(int argc, char **argv) {
  char line[128];

  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(auth));
          memset(auth, 0, sizeof(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 == service) {
              printf("you have logged in already!\n");
          } else {
              printf("please enter your password\n");
          }
      }
  }
}

cool little trick i have learned about this script, is that sometimes c programs that take user input will be stuck in a constant loop, through the while without asking for user input

not sure what causes this, but this script provided us with a solution:

int main(int argc, char**argv {
    char line[128];
    while(1) {
        if(fgets(line, sizeof(line), stdin)==NULL) break;
    }
}

this should ensure that the program will not continue until it recieves input

just thought it was a neat little trick since nowhere on the internet does it mention this problem im sure in some obscure corner there is probably somebody who can deal with info, but i guess i found it here anyways

anyways, on with reversing the binary, we have the source code so we could just look at it but that isnt the point of these writeups, i want to understand everything about this vulnerability and about this binary.

Lets explain what the program is doing from the source code, then match that up with the disassembly

struct auth {
  char name[32];
  int auth;
};

struct auth *auth;
char *service;

first, it will create a struct called auth, it will have a char array for the name, and an int auth then we see the struct being initialized, a pointer to an object of struct auth along with a pointer to a char array named service

now we are at main

int main(int argc, char **argv) {
  char line[128];
  while(1) {
      printf("[ auth = %p, service = %p ]\n", auth, service);

      if(fgets(line, sizeof(line), stdin) == NULL) break;

this first section of main will declare our buffer to read our input into with the fgets this will constantly print out our program status, then read in user input and store it in line no stack buffer overflow vulnerability here, will only read in 128 bytes

      if(strncmp(line, "auth ", 5) == 0) {
          auth = malloc(sizeof(auth));
          memset(auth, 0, sizeof(auth));
          if(strlen(line + 5) < 31) {
              strcpy(auth->name, line + 5);
          }
      }

lets check out the auth check next, it will check if our input is equals to the auth keyword things may get confusing so let me walk through all the instances of auth we have a pointer to a struct called auth, and within that struct we have an integer variable called auth, this is very poor naming btw.

anyways, it will allocate the sizeof auth on the heap, and return the pointer to auth the only way to know how many bytes it allocated, is to read the dissassembly it will then zero out auth.

Then will check if the line+5 is less than 31? this is very random, and i sure hope it serves a purpose later on if that passes the check, it will then copy that string into auth->name on the heap i presume it will copy line+5?, line is a char array, so either they are talking about the address of the char array in memory or i am missing something

anyways lets continue, we can come back to this strange scenario

      if(strncmp(line, "reset", 5) == 0) {
          free(auth);
      }

this check is simple, it only reads in "reset", and nothing more, if we input reset it will free auth, completely free it off the stack(may still be stored in thread local cache(tcache))

      if(strncmp(line, "service", 6) == 0) {
          service = strdup(line + 7);
      }

service was our pointer to a char array(string) at the start remember? lets just think of service as a string, now it will duplicate line+7 into service? again, my best guess as of right now is that line+7 is incrementing the pointer address by value 7 in decimal.

      if(strncmp(line, "login", 5) == 0) {
          if(auth == service) {
              printf("you have logged in already!\n");
          } else {
              printf("please enter your password\n");
          }

last, we have our login function, this as well only reads in 5 bytes, the size of the string "login" it will check if the auth struct pointer is equals to service

i assume that our end goal will be to achieve "you have logged in already", since that seems to be the only string in this binary that seems to represent success and fail

now that we have walked through the code, lets try and find a way to exploit this this should be a use after free, since we have the ability to free an object, and use the object on our command

      if(strncmp(line, "auth ", 5) == 0) {
          auth = malloc(sizeof(auth));
          memset(auth, 0, sizeof(auth));
          if(strlen(line + 5) < 31) {
              strcpy(auth->name, line + 5);
          }
      }

again, our auth command just for reference, will allocate the size of our auth object memset/zero out that memory on the heap since malloc does not initialize the data

"we can use calloc to automatically zero out that memory for us, though i dont know if that function will bring in some complications within the challenge"

it will then check the length of the input, and see if it exceeds the size of auth->name if all is ok, it will then copy the characters after the auth command will be copied "auth " will be ignored

reset is simple and needs no further explanation, it will read in "reset", then free auth nothing else

service is simple as well, so we dont need to step through it, though its got a lot of moving pieces that need explanations. The service function will read in 6 bytes, the size of "service" then will change our "string" pointer to strdup(line+7), which means that it will read in the bytes AFTER your command "service"

lets think of this scenario for example, lets run the program and input:

service loser

the address of that input string(line), will have an address space, and that address space will be in base 16(hexadecimal), if we wanted to iterate through that, we could simply move up that address space since it is a string, or an array of chars it will store that next char in sequential order in memory. which means that:

line    = service loser
line+7  = loser

since service is 6 bytes, and the space counts as a character, we are now at the input after we said service. Im sure that there is something very very wrong with this solution, but im not one to prod around with best practice, this works just fine in this scenario i guess :/

the last "function" we have within the binary is "login" this is pretty simple as well, it will only read in 5 bytes, the size of login to compare it against our input, if auth->auth is not zero, then it will tell us that

you have logged in already

else it will prompt us with

please enter your password

that is the only functionality that serves

now that we have fully reviewed these functions(again), since the first time wasnt so coherent lets(actually) exploit this binary now

first, we look at what "strdup" does, since this is an important function in helping us exploit this

"Memory for the new string is obtained with malloc(3), and can be freed with free(3)"

so it allocates the new string in the heap?, very interesting, very interesting indeed so we have a way to write what we want onto the heap

so now that we know this, the solution to the challenge is this:

  1. we must first initialize the auth struct on the heap, since login requires the pointer to it

  2. then, after our pointer to auth gets updated, we can free it a quick note: free() will not destroy the pointer, so after we free() auth, the pointer will still remain, so it still believes that auth->name and auth->auth still exists on that address but we know better right?, we know that we had just free'd it off the heap, its gone, only the pointer to that empty address remains lets play with that

  3. after our free, we can write to the heap with "service" we will be able to write to the same area that auth was allocated, since we had free'd it we will be able to write over the addresses to where auth is pointing to now all we have to do, is to write to the heap using service:

Boom! and we are logged in already!

[ auth = (nil), service = (nil) ] 
auth a
[ auth = 0x87c9818, service = (nil) ] 
reset
[ auth = 0x87c9818, service = (nil) ] 
service aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[ auth = 0x87c9818, service = 0x87c9828 ] 
login 
you have logged in already!

Last updated