Protostar 0x0D - Heap2
Prev: 0x0C - Heap1
Next: 0x0E - Heap3 pt 1
Per the instructions, this level "examines what can happen when heap pointers are stale." What does that mean?
Let's start with the source code:
This program runs an infinite loop (via "while(1)") which continually requests user input. The input is then checked (using strncmp), and the gist of this "program" can be deduced by examining these compares:
Next: 0x0E - Heap3 pt 1
Per the instructions, this level "examines what can happen when heap pointers are stale." What does that mean?
Let's start with the source code:
This program runs an infinite loop (via "while(1)") which continually requests user input. The input is then checked (using strncmp), and the gist of this "program" can be deduced by examining these compares:
- "auth" allocates space for an "auth" struct on the heap via malloc (sort of... to be clarified later), and any additional information we pass is set as the "name" variable within this "auth" struct.
- "reset" frees "auth" (or rather, the last "auth" that was called).
- "service" uses strdup to duplicate our string. When we check the man page, we see that this is done using "malloc," so it will be placed on the heap alongside our "auth" data.
- "login" attempts to login. If the "auth" integer in the "auth" struct has any data in it, we reach our win condition which is to receive the "you have logged in already!" message.
How are we supposed to set the "auth" integer if it isn't referenced anywhere in the program, aside from the initial declaration? We could try to overflow the "name" variable which precedes it, but unfortunately the bounds checking there is pretty tight via the "if(strlen(line + 5) < 31)" check. So what else can we do?
Well, this level references stale pointers, and indeed if we use "auth" to create an "auth" variable on the heap and then "reset" to free it, we see that "auth" still has its pointer:
$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth TEST
[ auth = 0x804c008, service = (nil) ]
reset
[ auth = 0x804c008, service = (nil) ]
This is a significant vulnerability called "use after free," in which a pointer to freed memory remains in use by a program. It is even listed on CWE's Top 25 Most Dangerous Software Errors for 2019.
How is this advantageous for us? Well, what if we now use "service" to allocate a new chunk of memory on the heap? Since the prior "auth" memory was freed, it's fair game!
service TEST
[ auth = 0x804c008, service = 0x804c008 ]
Now, they point to the same location in memory! This means that, if we enter "service" with a long enough input to be copied via strdup, we can pass the "login" check. Remember, "auth->auth" is interpreted as "the pointer to the struct 'auth' plus an offset of 0x20 bytes which is where integer auth is expected to be."
So, we just need to call "auth," free "auth," then enter "service" with more than 32 bytes of additional input? Well, not even that much - we can see that, if we try to do so, service doesn't receive auth's pointer, and instead it receives a pointer 0x10 bytes away.
serviceAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI
[ auth = 0x804c008, service = 0x804c018 ]
We know that "auth" was malloc'd using "sizeof(auth)" which we expect to be a 36 bytes ("name" plus 4 bytes for the "auth" integer). If we call "service" with less input than that, we should reuse auth's old memory chunk on the heap and receive the same pointer!
Well, based on the fact that the next chunk is located only ten bytes ahead, it seems that "sizeof(auth)" must have been interpreted as the size of the integer auth, and thus entering "auth" only allocates 16 bytes of memory.
Oops - definitely an error in the code, and likely not an intended one. However, we can still achieve our desired result, but this time with even less input! If "auth->auth" is supposed to be at 0x804c028, and "service" starts at 0x804c018, then we only need to provide 17 bytes of input.
$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth test
[ auth = 0x804c008, service = (nil) ]
reset
[ auth = 0x804c008, service = (nil) ]
serviceAAAABBBBCCCCDDDDE
[ auth = 0x804c008, service = 0x804c018 ]
login
you have logged in already!
Success!