写了一个简单的code,gcc -S test.c
int test_1() { } int test_2() { int a[0]; }
test_1: pushl %ebp movl %esp, %ebp nop popl %ebp ret test_2: pushl %ebp movl %esp, %ebp subl $24, %esp movl %gs:20, %eax movl %eax, -12(%ebp) xorl %eax, %eax nop movl -12(%ebp), %edx xorl %gs:20, %edx je .L3 call __stack_chk_fail .L3: leave ret
因为C不会对数组长度进行检测,所采取的方法就是插入一个金丝雀值,数组操作结束之后,看这个金丝雀值是否变化,变化了则调用
__stack_chk_fail,abort这个进程。
数组越界写入,导致 canary值被修改。在函数退出时检查canary,发现canary被修改,函数不能安全返回,call到__stack_chk_fail函数,abort当前
进程,使当前进程不能正常返回。
函数调用栈布局中canary位置
ESP:堆栈(Stack)指针寄存器,指向堆栈顶部
EBP:基址指针寄存器,指向当前堆栈底部
局部变量往低位地址增长,调用栈也是往位地址增长
canary位置:高于局部变量,低于EBP
所以诱发金丝雀的原因可能是:函数比局部变量更高的地址被修改
栈分配是从上到下分配,但是用的时候,是从下到上用的。
int main() { int aa[10] = {0}; printf("%p\n", &(aa[0])); printf("%p\n", &(aa[1])); printf("%p\n", &(aa[2])); }
$ ./test
0xbf94c2b4
0xbf94c2b8
0xbf94c2bc
长度为0的数组正确用法应该是如下所示的情况:
struct TEST { int aa; int bb[0]; }; #define ARR_SIZE XXX int main() { struct TEST test = malloc(sizeof(struct TEST) + ARR_SIZE); /*然后通过bb访问后边的数据*/ /*...*/ return 0; }