First Exploit Q&A - kkli08/Buffer-Overflow GitHub Wiki

Writing the first Exploit

// overflow1.c

char shellcode[] = 
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" 
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" 
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";

char large_string[128];

void main() {
    char buffer[96];
    int i;
    long *long_ptr = (long *) large_string;
    for (i = 0; i < 32; i++) 
        *(long_ptr + i) = (int) buffer;

    for (i = 0; i < strlen(shellcode); i++) 
        large_string[i] = shellcode[i];

    strcpy(buffer,large_string); 
    }

==========================================================
[aleph1]$ gcc -o overflow1 overflow1.c 
[aleph1]$ ./overflow1
$ exit // successfully spawn a shell
exit
[aleph1]$
==========================================================

Q&A

Why we need duplicating the buffer's address in large_string?

  1. Pointer Assignment:

    • long_ptr is used as a pointer to large_string. The loop that follows writes the address of buffer into each slot of large_string. This is done by casting large_string to a long * and then setting each long sized chunk of large_string to the address of buffer.
  2. Reason for Duplicating Buffer's Address:

    • The duplication of the buffer's address 32 times in large_string is a part of setting up the buffer overflow. The goal is to ensure that when large_string is copied into buffer, it overflows buffer and overwrites adjacent memory locations, which include the function's return address on the stack.
  3. Shellcode Injection:

    • The next step, where large_string[i] = shellcode[i] is executed, places the actual shellcode at the beginning of large_string. This means that the start of large_string (which is now being copied into buffer) contains the shellcode.
  4. Combining the Two Steps:

    • The reason for initially filling large_string with the address of buffer and then placing the shellcode at the beginning is to achieve two objectives:
      1. Shellcode Placement: Ensure that the shellcode is at the beginning of the overflowed buffer (buffer). This is necessary for the shellcode to be executed.
      2. Overwrite Return Address: By overflowing buffer with large_string (which contains the buffer's address repeated many times), it aims to overwrite the return address on the stack with the address of buffer (where the shellcode now resides).
  5. Execution Flow:

    • When the function returns, if the return address on the stack has been successfully overwritten with the address pointing to the shellcode (now at the start of buffer), the execution will jump to the shellcode instead of returning to the original calling function.

Key Point 1

  • Partial Overwrite with Shellcode: The shellcode itself does not need to occupy the entire large_string. It only needs enough space at the beginning. The rest of large_string (filled with the address of buffer) ensures that when the overflow happens, the overwritten return address points to the location where the shellcode begins (which is now within buffer).

Ideally the shellcode length should smaller than the length of the buffer, is that right?

Ideally, yes, the length of the shellcode should be smaller than the length of the buffer you are overflowing in a buffer overflow exploit. This is important for a few reasons:

  1. Fit Within the Buffer: The shellcode needs to fit within the buffer before the buffer overflows. This allows the shellcode to be placed at the beginning of the buffer, ensuring it is in the right position when the overflow occurs.

  2. Overwrite Return Address: The primary goal of a buffer overflow is to overwrite the return address on the stack. To do this, the buffer must overflow beyond its allocated space. If the shellcode is longer than the buffer, it would not leave enough space to fill the rest of the buffer (and the subsequent stack space) with the address that points to the shellcode (or a NOP sled leading to the shellcode).

  3. Control Execution Flow: The exploit aims to control the execution flow by redirecting it to the shellcode. For this to work effectively, the shellcode must be precisely positioned so that the overwritten return address points to it.

  4. Avoid Crashes and Detection: A shorter shellcode reduces the risk of crashing the program or the operating system and might help in avoiding detection by security mechanisms.

Practical Considerations

  • Buffer Size Limitation: In real-world scenarios, the buffer size you are exploiting is often fixed and can be relatively small, so efficiency in shellcode design is crucial.

  • Use of NOP Sleds: Sometimes, a NOP (No Operation) sled is used, which is a series of NOP instructions leading up to the shellcode. This can make it easier to hit the shellcode as the return address does not need to be as precise – any address landing in the NOP sled will slide down to the shellcode.

  • System and Architecture Constraints: The success of such an exploit depends on various factors, including system architecture, memory layout, and security mechanisms in place (like stack canaries, ASLR, DEP, etc.).