GCC Stack Protection Introduction
I originally was going to write a post on some new OS X shellcode, but after getting a vulnerable test app going, I thought it would be worthwhile to write a brief introduction to the built-in stack buffer overflow protection in GCC (4.2.1 on Snow Leopard for me).
Here’s the basic code:
int main(int argc, char** argv)
{
char dest[8] = { 0 };
strcpy(dest, argv[1]);
}
Here’s what you get depending on whether you compile with the -fno-stack-protector
option (on the left) or without it.
prologue: |
prologue: |
The left side is full of win (or lose, depending on your perspective). Nothing but a straight unbounded strcpy onto a stack buffer. Too large an input parameter and you get EIP.
On the right side, we can see the addition of the canary setup and post-strcpy check. Brief explanation of the canary code: The __stack_chk_guard
value is located and stored on the stack (This value is randomized by the C runtime). The stored value is checked after the call to ensure that the canary wasn’t is still present on the stack. If the check fails, the thread terminates without allowing a potentially-corrupted return address to be used.
One issue you might have noticed if you compiled the above code: GCC actually emits a warning:
unsafe.c: In function ‘main’:
unsafe.c:7: warning: incompatible implicit declaration of built-in function ‘strcpy’
True — our code doesn’t #include <string.h>
. Adding that should just eliminate the warning, not change anything, right? Take a look when we disassemble after adding the include.
prologue: |
prologue: |
Aha! Using the string.h version (rather than the implicit version) results in using __strcpy_chk
rather than strcpy
. The prototype for this method is:
void * __strcpy_chk (char *dest, char *src, size_t dstlen)
.
In the highlighted lines above, 0x8 (size of the buffer on the stack) is pushed and passed to __strcpy_chk
. The src string is checked at runtime, and __chk_fail()
is called to terminate the thread without overflowing the buffer.
This little exercise brings two principles to mind:
- Be secure by default. GCC adds stack protection by default, and it must be explicitly turned off.
- Don’t ignore compiler warnings. The warning message isn’t clear, but there were security advantages to the fix.
PS: If the size calculation is constant, the same sort of checks can be added for heap-buffers. I haven’t looked to see how GCC handles dynamically-sized heap buffers.