Skip to content

GCC Stack Protection Introduction

October 6, 2010

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:
push %ebp
mov %esp,%ebp
sub $0x28,%esp

strcpy:
movl $0x0,-0x10(%ebp)
movl $0x0,-0xc(%ebp)
mov 0xc(%ebp),%eax
add $0x4,%eax
mov (%eax),%eax
mov %eax,0x4(%esp)
lea -0x10(%ebp),%eax
mov %eax,(%esp)
call 0x1f8a <dyld_stub_strcpy>

epilogue:
leave
ret
prologue:
push %ebp
mov %esp,%ebp
push %ebx
sub $0x34,%esp
call 0x1f1e <main+12>

canary_setup:
pop %ebx
mov 0xc(%ebp),%eax
mov %eax,-0x1c(%ebp)
lea 0xfe(%ebx),%eax
mov (%eax),%eax
mov (%eax),%edx
mov %edx,-0xc(%ebp)
xor %edx,%edx


strcpy:
movl $0x0,-0x14(%ebp)
movl $0x0,-0x10(%ebp)
mov -0x1c(%ebp),%eax
add $0x4,%eax
mov (%eax),%eax
mov %eax,0x4(%esp)
lea -0x14(%ebp),%eax
mov %eax,(%esp)
call 0x1f80 <dyld_stub_strcpy>

canary_check:
lea 0xfe(%ebx),%edx
mov (%edx),%edx
mov -0xc(%ebp),%ecx
xor (%edx),%ecx
je 0x1f6d <main+91>
call 0x1f74 <dyld_stub___stack_chk_fail>


prologue:
add $0x34,%esp
pop %ebx
leave
ret

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:
push %ebp
mov %esp,%ebp
sub $0x28,%esp

strcpy:
movl $0x0,-0x10(%ebp)
movl $0x0,-0xc(%ebp)
mov 0xc(%ebp),%eax
add $0x4,%eax
mov (%eax),%eax
movl $0x8,0x8(%esp)
mov %eax,0x4(%esp)
lea -0x10(%ebp),%eax
mov %eax,(%esp)
call 0x1f84 <dyld_stub___strcpy_chk>

epilogue:
leave
ret
prologue:
push %ebp
mov %esp,%ebp
push %ebx
sub $0x34,%esp
call 0x1f16 <main+12>

canary_setup:
pop %ebx
mov 0xc(%ebp),%eax
mov %eax,-0x1c(%ebp)
lea 0xfe(%ebx),%eax
mov (%eax),%eax
mov (%eax),%edx
mov %edx,-0xc(%ebp)
xor %edx,%edx

strcpy:
movl $0x0,-0x14(%ebp)
movl $0x0,-0x10(%ebp)
mov -0x1c(%ebp),%eax
add $0x4,%eax
mov (%eax),%eax
movl $0x8,0x8(%esp)
mov %eax,0x4(%esp)
lea -0x14(%ebp),%eax
mov %eax,(%esp)
call 0x1f7a <dyld_stub___strcpy_chk>

canary_check:
lea 0xfe(%ebx),%edx
mov (%edx),%edx
mov -0xc(%ebp),%ecx
xor (%edx),%ecx
je 0x1f6d <main+91>
call 0x1f74 <dyld_stub___stack_chk_fail>
add $0x34,%esp
pop %ebx
leave
ret

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.

From → Uncategorized

Leave a Comment

Leave a comment