Information Security
buffer-overflow c assembly
Updated Mon, 11 Jul 2022 03:43:23 GMT

How does this simple buffer overflow work?

I've got this simple code


#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
    char buffer[500];
    strcpy(buffer, argv[1]);
    printf("%s", buffer);
    return 0;

I am trying to perform a buffer overflow and

  • Fill the buffer with a malicious code
  • Modify the return address to redirect to the malicious code

Ideally, I believe that when I overwrite my 500 buffer, next I will be overwriting the base pointer, followed by the return address.

Here's how I tried to overflow the buffer

gcc vuln.c
./a.out $(python -c 'print "\x41" * 501')

Since I am putting 501 "A"s into the buffer, it should overflow theoretically, right? But this is not happening. I don't get a segmentation fault, instead, I get the output of "AAAA.." and the program exits normally. (I didn't count the number of As..).

So I fired up GDB and start playing with the number of As until I do get a segmentation fault. And I find that I get a segmentation fault when I put 520 "A"s! How?

With 520 A's, GDB's info registers gives me enter image description here

And with 521 A's, running the same GDB command, I get enter image description here

As you can see, in image 1, the base point is filled with \x41 (hex for A) and in image 2, the BP is filled with \x41s and the IP begins to be filled as well.

I don't understand this. So my question is - why 520? And why did it not overflow on 501?


That's due to an alignment to 16 bytes, which compilers do on x86(_64) for compatibility with SIMD instructions that operate on 128 bits (16 bytes). Due to that there is some "padding" between the buffer and the saved registers, 12 bytes in your case.

Technically, you already overflow the buffer if you pass 500 A characters to the program because the string is null-terminated. But that zero byte only overwrites the first of the padding bytes. Between these padding bytes and the saved rip there is also the saved rbp (8 bytes). So the layout is basically like this (if canaries are in use - -fstack-protector - then the canary value is placed between the padding and saved registers):

buffer [500 bytes] | padding [12 bytes] | saved rbp [8 bytes] | saved rip [8 bytes]

So with 520 A characters you overwrite first the padding and the saved rbp before the first byte of the saved rip is overwritten with a zero byte.

Comments (4)

  • +0 – why do you mean that he technically already overflow the buffer? Like you mentioned in the first part there is that padding and so the buffer had still enough space. — Nov 08, 2018 at 16:10  
  • +1 – Interesting, thank you, great answer! I had a tiny doubt with your last statement. With 520 A characters, I overwrite first the buffer, the padding and the saved rbp, but shouldn't I also be overwriting 1 byte of the rip as well? Since 499 fits in the buffer, 500-511 would go into the padding, 512-519 into rbp and 520 into rip. Am I correct? @Nightscape: I think he meant that by writing 500 itself, I am technically overflowing the variable 'buffer'. I begin writing over the padding, yes, but to do that, I had to first overflow the buffer itself. — Nov 09, 2018 at 04:55  
  • +0 – Yes, that's what I was referring to. Because of the null-termination of C strings you already write one byte too much when writing a string of length x (without the null byte) to an x-byte-sized buffer. I wrote "technically" because that one-byte overflow has no practical impact in this case due to the padding (but that depends on the size of the bufffer). — Nov 09, 2018 at 06:28  
  • +1 – Regarding my last statement and your interpretation: If you strcpy a string of 520 A characters to the buffer you actually copy 521 characters due to the null-termination, so yes, that null byte does overwrite one byte of the saved rip (the least significant byte because it's little-endian). — Nov 09, 2018 at 06:30