栈基础知识
2018-05-24 本文已影响0人
看点书
1.C语言变量的分布 :
C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码:
#include <stdio.h>
int g1=0, g2=0, g3=0;
int main()
{
static int s1=0, s2=0, s3=0;
int v1=0, v2=0, v3=0;
//打印出各个变量的内存地址
printf("0x%08x\n",&v1); //打印各本地变量的内存地址
printf("0x%08x\n",&v2);
printf("0x%08x\n\n",&v3);
printf("0x%08x\n",&g1); //打印各全局变量的内存地址
printf("0x%08x\n",&g2);
printf("0x%08x\n\n",&g3);
printf("0x%08x\n",&s1); //打印各静态变量的内存地址
printf("0x%08x\n",&s2);
printf("0x%08x\n\n",&s3);
system("pause");
return 0;
}
可以看出本地变量和全局/静态变量的分布完全不同,相差甚远,这是因为他们分布在不同类型的区域。
进程的内存空间分为:代码区,静态数据区和动态数据区。全局和静态变量分配在静态数据区,本地变量分配在动态数据区,即”堆栈“,
image
2. 栈的存储
#include <stdio.h>
void __stdcall func(int param1,int param2,int param3)
{
int var1=param1;
int var2=param2;
int var3=param3;
printf("0x%08x\n",¶meter1); //打印出各个变量的内存地址
printf("0x%08x\n",¶meter2);
printf("0x%08x\n\n",¶meter3);
printf("0x%08x\n",&var1);
printf("0x%08x\n",&var2);
printf("0x%08x\n\n",&var3);
return;
}
int main()
{
func(1,2,3);
return 0;
}
image
函数的参数是从右向左传递,即先压栈parameter 3,然后parameter 2,最后才是parameter 1,然后是函数的返回地址,然后就是本地变量var1,var2,var3
3.程序进入main()函数 ,栈帧的保存和关闭
例如:
int main()
{
return0;
}
汇编代码为:
push ebp; 保存进入main()函数时其他初始化函数的栈底
move ebp,esp; 把当前esp的值作为栈底
sub esp ,40h 开辟栈空间,作为局部变量的存储空间
push ebx
push esi
push edi 保存寄存器的值
LEA edi ,[ebp-40h] 取出此函数可用栈空间首地址
mov ecx,10h 设置ecx寄存器的值
mov eax ,occcccccch 把局部变量初始化为0xcccccccch
rep stos dword ptr [edi] 根据ecx的值,把eax的内容,以四字节为单位写到edi指向的内存
xor eax,eax 设置返回值为0
pop edi
pop esi
pop ebx 弹出压入寄存器的值
add esp,40h 降低esp,局部空间释放
cmp ebp,esp 检查栈平衡
call _chkesp() 进入栈错误检查函数
mov esp.ebp 还原esp
pop ebp 还原ebp
ret
4. 简单的分配栈帧及溢出修改相邻变量
例如:
#include <windows.h>
#define PASSWORD "1234567"
int verify_password(char *password){
int authenticated;
char buffer[8];
authenticated = strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;
}
int main(int argc, char* argv[])
{
int valid_flag = 0;
char password[1024];
FILE *fp;
if (!(fp=fopen("password.txt","rw+"))){
return 0;
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag){
printf("incorrect password!\n");
}else{
printf("Congratulation! You have passed the verification !\n");
}
Sleep(-1);
return 1;
}
用OD调试:
进入main()主函数,找到验证密码的函数调用位置,进入到函数具体代码处:
image
image
image
前面部分就是栈分配局部变量空间和初始化的过程,然后就是字符串的计较,最后是字符串的复制,分析可得栈溢出在这一部分,在指令008D1409处把函数的返回值(EAX储存的是返回值)存在了EBP-0XC处,下面就是strocpy的操作,char buffer[8]分配了八个字节的存储空间,但是password.txt的密码如图为24个字节,知错执行strcpy的时候,把buffer 附近的变量空间也给覆盖了,比如返回值的。以上过程如图所示
image
image
image