测试代码
代码很简单,就是把函数指针转换成int类型储存在数组的边界之外
数组在C语言中以连续的方式储存在堆栈中,所以声明数组时必须确定长度,但是在调用数组时编译器不会检查数组的边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<iostream>
void func() { while (1) { std::cout<<"缓冲区溢出"<<std::endl; } }
int check() { int arr[8] = {1,2,3,4,5,6,7,8}; arr[10] = (int)&func; return 0; }
void main() {
__asm { mov eax, eax mov eax, eax mov eax, eax }
check(); getchar();
return; }
|
直接运行结果如下

分析
可以看到func()函数并没有被调用但还是执行了,在高级语言层面上无法得知原因,只能通过反汇编分析堆栈平衡。
关键处反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| push ebp mov ebp,esp sub esp,0E8h push ebx push esi push edi lea edi,[ebp-28h] mov ecx,0Ah mov eax,0CCCCCCCCh rep stos dword ptr es:[edi] mov ecx,97F029h call 0097138E 10: int arr[8] = {1,2,3,4,5,6,7,8} mov dword ptr [ebp-24h],1 mov dword ptr [ebp-20h],2 mov dword ptr [ebp-1Ch],3 mov dword ptr [ebp-18h],4 mov dword ptr [ebp-14h],5 mov dword ptr [ebp-10h],6 mov dword ptr [ebp-0Ch],7 mov dword ptr [ebp-8],8 11: arr[10] = (int)&func mov eax,4 imul ecx,eax,0Ah mov dword ptr [ebp+ecx-24h],971352h 12: return 0 xor eax,eax 13: } push edx mov ecx,ebp push eax lea edx,ds:[009724A4h] call 00971230 pop eax pop edx pop edi pop esi pop ebx add esp,0E8h cmp ebp,esp call 00971294 mov esp,ebp pop ebp ret
|
从反汇编可以得知从EBP-0X28到EBP之间为缓冲区,对每个字节填充0XCC,vs的编译器生成的局部变量是从EBP-8开始的,所以想要造成缓冲区溢出应该将下标取10
mov dword ptr [ebp+ecx-24h],971352h这条指令将函数地址被储存到了EBP+4,EBP指向的内存储存的是堆栈提升前的EBP,而EBP+4储存的是函数调用前的下一步地址
当执行到
1 2 3
| mov esp,ebp pop ebp ret
|
堆栈平衡过程中,ESP会还原成EBP的值,然后弹出原EBP的值都是正常的(ESP此时指向的地址储存着原EBP的值)
而当执行RET指令时,ESP弹出后+4,此时ESP正是指向那个函数的地址
RET和CALL指令都会修改EIP寄存器,最终RET指令将ESP的值写入到EIP然后跳转
