I am trying to write an example program to understand stack buffer overflow and i have the following program.
overflow.s:
.section .data
.section .text
.globl _start
_start:
call sum
movl %eax, %ebx
movl $15, %ebx
movl $1, %eax
int $0x80
.type sum, @function
sum:
pushl %ebp # save the current base pointer
movl %esp, %ebp # store current stack pointer to %ebp
subl $4, %esp # inc the stack pointer by 4 bytes for local variable
movl $5, -8(%ebp) # store value 5 from 8 bytes of %ebp 4 bytes beyond stack pointer
addl $5, -8(%ebp) # add 5 to the value store beyond of stack pointer
movl -8(%ebp), %eax # store the value in %eax
movl %ebp, %esp
popl %ebp
ret
assemble and link the program:
as -gstabs+ overflow.s -o oveflow.o
ld overflow.o -o overflow
./overflow
echo $?
15 <============= the result
I expected either i get some garbage or segfault. but it seems to work as expected. So in the sum function when i increment the stack pointer by 4 bytes and when i storing the value 5 8 bytes from base pointer, I was expecting this is a simulation of overflow. Is the above program wrong to be used an example of stack buffer overflow. ?
Memory below %esp
might be clobbered asynchronously (by a signal handler1), but the behaviour of your program doesn't depend on the values you read/write with addl $5, -8(%ebp)
or movl -8(%ebp), %eax
, in that 4 byte stack slot right below ESP.
On Linux, it's not an error to touch memory below ESP, at least within the same page. Farther than that and it might segfault instead of growing the stack (for the main thread's stack) unless the stack pointer is moved first. (Thread stack will already be fully allocated, not grown on the fly. The initial process stack is special.)
If you ran push
in a loop (without a pop
or anything else to balance it), the stack would grows until ESP decreased to point to an unmapped page beyond the point where the kernel will grow the stack for you. Then the next push
(or call
or whatever) would segfault, and we'd call that a stack overflow.
A buffer overflow would be if you did sub $12, %esp
to reserve space for int arr[3]
, but then wrote to int arr[5]
: that would overwrite your return address so the eventual ret
would jump to wherever the attacker wanted you to jump.
# arg in EAX: how many array elements to store into arr[3]
vulnerable_function:
sub $12, %esp
mov %esp, %ecx
.Lloop:
mov %eax, (%ecx)
add $4, %ecx
dec %eax
jnz .Lloop
add $12, %esp
ret
Footnote 1: You haven't installed any signal handlers, so (on Linux) nothing can use stack memory asynchronously, and you have an unlimited red-zone below ESP.
But this is a special case when you're writing a complete program and not using any libraries, normally you should just inline small functions if the extra instructions to reserve and release stack space have non-trivial cost.
Also, debuggers can step on this space if you do print foo()
in GDB or something to have it call a function while stopped at a breakpoint somewhere.