栈保护 栈溢出 stack smashing detected 定位方法
一、Linux开发环境
OS:Ubuntu 16.04.1
编译工具:gcc 5.4.0
调试工具:gdb
二、关键词
栈保护
栈溢出
*** stack smashing detected ***
-fstack-protector
-fno-stack-protector
三、背景
gcc提供栈溢出保护机制,即默认编译时-fstack-protector选项为开。
在该保护机制下,如果程序中有栈溢出,会有以下报错信息,程序异常终止:
*** stack smashing detected ***: ./test terminated
Aborted (core dumped)
若要关闭栈溢出保护,在gcc编译选项中增加-fno-stack-protector即可。
关闭后,如果程序中有栈溢出,仍可能会成功执行完,没有任何报错。
但实际上,栈溢出会导致程序中定义的变量被篡改。
如果你的程序出现上面提到的两种异常:变量被篡改、或者栈溢出导致的异常终止,希望本文会对你有帮助。
四、定位分析
1)第一种情况:变量的值被篡改。
a)源代码(可不看)
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "math.h"
#include <ctype.h>
int c2i(char ch)
{
//如果是数字,则用ascii 减去48
//isdight 判断数字函数
if(isdigit(ch))
{
return ch-48;
}
//判断字母
if(ch<'A'||(ch>'F'&&ch<'a')||ch>'z')
{
return -1;
}
//大写字母减去55,小写字母减去87
if(isalpha(ch))
{
return isupper(ch)?ch-55:ch-87;
}
}
int hex2dec(char *hex)
{
int len, num,temp,bits,i;
num = 0;
len = strlen(hex);
//printf("len = %d\n",len);
for(i=0,temp=0;i<len;i++,temp=0)
{
//printf("hex = %c\n",hex[i]);
//printf("num = %d\n",num);
temp = c2i(*(hex+i));
//printf("temp = %d\n",temp);
bits = (len-i-1)*4;
//printf("bits = %d\n",bits);
temp = temp << bits;
//printf("temp = %d\n",temp);
num = num|temp;
}
return num;
}
char* itoa(long int num,char *str,int radix)
{
char index[] = "0123456789ABCDEF";
long unsigned unum;
int i=0,j,k;
if(radix==10&&num<0)
{
unum = (unsigned) -num;
str[i++] = '-';
}
else
unum = (unsigned)num;
do
{
str[i++] = index[unum%(unsigned)radix];
unum/=radix;
}while(unum);
//printf("str = %s\n",str);
str[i] = '\0';
if(str[0]=='-')
{
k = 1;
}
else
k = 0;
for(j = k;j<=(i-1)/2;j++)
{
char temp;
temp = str[j];
str[j] = str[i-1+k-j];
str[i-1+k-j] = temp;
}
//printf("str = %s\n",str);
return str;
}
/******************************************************
生成command 的chksum函数
*******************************************************/
char *creatChksum(char *Commandinfo,char* chkstr)
{
int temp = 0;
int chksum = 0;
for(int i=1;i<strlen(Commandinfo);i++)
{
temp = Commandinfo[i]+temp;
}
//printf("creatChksum = %s\n",Commandinfo);
//printf("creatChksum = %d\n",temp);
sprintf(chkstr,"%04X",temp);
return chkstr;
}
double getRandData(int min,int max)
{
double m1=(double)(rand()%101)/101; // 计算 0,1之间的随机小数,得到的值域近似为(0,1)
min++; //将 区间变为(min+1,max),
double m2=(double)((rand()%(max-min+1))+min); //计算 min+1,max 之间的随机整数,得到的值域为[min+1,max]
m2=m2-1; //令值域为[min,max-1]
return m1+m2; //返回值域为(min,max),为所求随机浮点数
}
int creatfloatstr(float num,char *str)
{
int rtn = 0;
long int tempdata;
float mantissa = 0;
int stepdata = 0;
char tempstr[24];
char mantissastr[24];
char strstep[9];
char strsteptemp[9];
char floatstr[33];
#if 1
if(rtn == 0)
{
memset(str,'\0',33);
//printf("%f\n",num);
if(rtn == 0)
{
for(int i=1;i<20;i++)
{
num = num/2;
if(num<2)
{
stepdata = i;
break;
}
}
mantissa = num -1;
memset(mantissastr,'\0',24);
long int mantissadata = mantissa*8388608;
itoa(mantissadata,tempstr,2);
if(strlen(tempstr)<23)
{
int length = 23 -strlen(tempstr);
for(int i=0;i<length;i++)
{
mantissastr[i] = '0';
}
strncat(mantissastr,tempstr,23);
}
else
{
strncpy(mantissastr,tempstr,23);
}
}
if(rtn == 0)
{
memset(strsteptemp,'\0',9);
memset(strstep,'\0',9);
itoa(stepdata,strsteptemp,2);
if(strlen(strsteptemp)<8)
{
int length = 8 - strlen(strsteptemp);
for(int i=0;i<length;i++)
{
strstep[i] = '0';
}
strcat(strstep,strsteptemp);
}
else
{
strncpy(strstep,strsteptemp,8);
}
//printf("strstep = %s\n",strstep);
}
if(rtn == 0)
{
if(num<0)
{
strcat(str,"1");
}
else
{
strcat(str,"0");
}
strcat(str,strstep);
strcat(str,mantissastr);
for(int i = 0;i<strlen(str);i++)
{
if(str[i] == '1')
tempdata = tempdata + pow(2,31-i);
}
sprintf(str,"%08lX",tempdata);
}
//printf("str = %s\n",str);
}
#endif
return rtn;
}
unsigned int virtual_data(char *receivebuf,char *sendbuf)
{
int rtn = 0;
int chkNumber = 0;
char chucksum[5];
char cid2[3];
char length[5];
int datalength=0;
char *commandinfo;
char buf[10];
char data[8];
int len = strlen(receivebuf);
memset(buf, '\0', 10);
memcpy(buf, "ABCDEFGH", 8);
printf("------- buf = %s, line[%d]\n", buf, __LINE__);
if(rtn == 0)
{
//首先chucksum
if(rtn == 0)
{
//字符串转数字,计算chksum值
memset(chucksum,'\0',4);
for(int i = 0;i<4;i++)
{
chucksum[i] = receivebuf[len-6+i];
}
chucksum[4] = '\0';
strncpy(chucksum,chucksum,4);
for(int i = 1;i<len-6;i++)
{
chkNumber = chkNumber + receivebuf[i];
//printf("receivebuf[i]=%c\n",receivebuf[i]);
}
chkNumber = chkNumber%65536;
chkNumber = ~chkNumber+1+65536;
if(chkNumber != hex2dec(chucksum))
{
//printf("chkNumber=%d\n",chkNumber);
//printf("hex2dec(chucksum)=%d\n",hex2dec(chucksum));
rtn = -1;
}
}
//解析设备类型,填充设备数据,主要解析cid2 length commandinfo chucksum
#if 1
if(rtn == 0)
{
//填充sendbuf
strncpy(sendbuf,receivebuf,7);
memset(cid2,'\0',3);
*(cid2) = *(receivebuf+7);
*(cid2+1) = *(receivebuf+8);
//printf("%s\n",cid2);
if(strcmp(cid2,"41") == 0)
{
//默认数据正常 rtn 赋值00
strncat(sendbuf,"00",2);
memset(length,'\0',5);
//数据为浮点型数据,处理数据长度
for(int i = 0;i<4;i++)
{
length[i] = receivebuf[10+i];
}
strncat(sendbuf,length,4);
length[3] = '\0';
//printf("length = %s\n",length);
datalength = hex2dec(length)/2;
//printf("datalength = %d\n",datalength);
for(int i=0;i<datalength;i++)
{
memset(data,'\0',9);
creatfloatstr(getRandData(0,100),data);
strncat(sendbuf,data,8);
}
//memset(sendbuf,'\0',200);
creatChksum(sendbuf,chucksum);
strncat(sendbuf,chucksum,4);
strncat(sendbuf,"CR",2);
}
if(strcmp(cid2,"42") == 0)
{
//默认数据正常 rtn 赋值00
strncat(sendbuf,"00",2);
//数据为整型数据,处理数据长度
for(int i = 0;i<4;i++)
{
length[3-i] = receivebuf[13+i];
}
strncat(sendbuf,length,4);
length[3] = '0';
datalength = hex2dec(length);
for(int i=0;i<datalength;i++)
{
memset(data,'\0',8);
sprintf(data,"%04X",rand()%101);
strncat(sendbuf,data,8);
}
memset(sendbuf,'\0',4);
creatChksum(sendbuf,chucksum);
strncat(sendbuf,chucksum,4);
strncat(sendbuf,"CR",2);
}
}
#endif
}
printf("------- buf = %s, line[%d]\n", buf, __LINE__);
return rtn;
}
int main()
{
int rtn = 0;
char *receivebuf = malloc(256*sizeof(char));
strncpy(receivebuf,"~20012441E00201FD3ACR",32*sizeof(char));
char sendbuf[200];
rtn = virtual_data(receivebuf,sendbuf);
//printf("send=%s\n",sendbuf);
exit(0);
return 0;
}
b)编译
gcc -g -o test main.c –lm
c)运行
发现buf中的数据被篡改!通过阅读程序,buf在virtual_data函数中定义,之后便再无赋值,那么应该 是在两次打印之间被篡改的。像这种情况,首先怀疑的就是发生了内存越界!
d)定位
e)总结
程序中能明确看到某个变量的值被篡改,这种情况一般定位思路:
使用watch命令观察该变量;
执行c继续运行,程序会停在观察点发生变化的地方;
执行where命令,查看当前的位置,即定位到哪行语句篡改了被观察的变量。
2)第二种情况:程序报”stack smashing detected”然后终止。
a)源代码(可不看)
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "math.h"
#include <ctype.h>
int c2i(char ch)
{
//如果是数字,则用ascii 减去48
//isdight 判断数字函数
if(isdigit(ch))
{
return ch-48;
}
//判断字母
if(ch<'A'||(ch>'F'&&ch<'a')||ch>'z')
{
return -1;
}
//大写字母减去55,小写字母减去87
if(isalpha(ch))
{
return isupper(ch)?ch-55:ch-87;
}
}
int hex2dec(char *hex)
{
int len, num,temp,bits,i;
num = 0;
len = strlen(hex);
//printf("len = %d\n",len);
for(i=0,temp=0;i<len;i++,temp=0)
{
//printf("hex = %c\n",hex[i]);
//printf("num = %d\n",num);
temp = c2i(*(hex+i));
//printf("temp = %d\n",temp);
bits = (len-i-1)*4;
//printf("bits = %d\n",bits);
temp = temp << bits;
//printf("temp = %d\n",temp);
num = num|temp;
}
return num;
}
char* itoa(long int num,char *str,int radix)
{
char index[] = "0123456789ABCDEF";
long unsigned unum;
int i=0,j,k;
if(radix==10&&num<0)
{
unum = (unsigned) -num;
str[i++] = '-';
}
else
unum = (unsigned)num;
do
{
str[i++] = index[unum%(unsigned)radix];
unum/=radix;
}while(unum);
//printf("str = %s\n",str);
str[i] = '\0';
if(str[0]=='-')
{
k = 1;
}
else
k = 0;
for(j = k;j<=(i-1)/2;j++)
{
char temp;
temp = str[j];
str[j] = str[i-1+k-j];
str[i-1+k-j] = temp;
}
//printf("str = %s\n",str);
return str;
}
/******************************************************
生成command 的chksum函数
*******************************************************/
char *creatChksum(char *Commandinfo,char* chkstr)
{
int temp = 0;
int chksum = 0;
for(int i=1;i<strlen(Commandinfo);i++)
{
temp = Commandinfo[i]+temp;
}
//printf("creatChksum = %s\n",Commandinfo);
//printf("creatChksum = %d\n",temp);
sprintf(chkstr,"%04X",temp);
return chkstr;
}
double getRandData(int min,int max)
{
double m1=(double)(rand()%101)/101; // 计算 0,1之间的随机小数,得到的值域近似为(0,1)
min++; //将 区间变为(min+1,max),
double m2=(double)((rand()%(max-min+1))+min); //计算 min+1,max 之间的随机整数,得到的值域为[min+1,max]
m2=m2-1; //令值域为[min,max-1]
return m1+m2; //返回值域为(min,max),为所求随机浮点数
}
int creatfloatstr(float num,char *str)
{
int rtn = 0;
long int tempdata;
float mantissa = 0;
int stepdata = 0;
char tempstr[24];
char mantissastr[24];
char strstep[9];
char strsteptemp[9];
char floatstr[33];
#if 1
if(rtn == 0)
{
memset(str,'\0',64);
//printf("%f\n",num);
if(rtn == 0)
{
for(int i=1;i<20;i++)
{
num = num/2;
if(num<2)
{
stepdata = i;
break;
}
}
mantissa = num -1;
memset(mantissastr,'\0',24);
long int mantissadata = mantissa*8388608;
itoa(mantissadata,tempstr,2);
if(strlen(tempstr)<23)
{
int length = 23 -strlen(tempstr);
for(int i=0;i<length;i++)
{
mantissastr[i] = '0';
}
strncat(mantissastr,tempstr,23);
}
else
{
strncpy(mantissastr,tempstr,23);
}
}
if(rtn == 0)
{
memset(strsteptemp,'\0',9);
memset(strstep,'\0',9);
itoa(stepdata,strsteptemp,2);
if(strlen(strsteptemp)<8)
{
int length = 8 - strlen(strsteptemp);
for(int i=0;i<length;i++)
{
strstep[i] = '0';
}
strcat(strstep,strsteptemp);
}
else
{
strncpy(strstep,strsteptemp,8);
}
//printf("strstep = %s\n",strstep);
}
if(rtn == 0)
{
if(num<0)
{
strcat(str,"1");
}
else
{
strcat(str,"0");
}
strcat(str,strstep);
strcat(str,mantissastr);
for(int i = 0;i<strlen(str);i++)
{
if(str[i] == '1')
tempdata = tempdata + pow(2,31-i);
}
sprintf(str,"%08lX",tempdata);
}
//printf("str = %s\n",str);
}
#endif
return rtn;
}
unsigned int virtual_data(char *receivebuf,char *sendbuf)
{
int rtn = 0;
int chkNumber = 0;
char chucksum[5];
char cid2[3];
char length[5];
int datalength=0;
char *commandinfo;
char buf[10];
char data[8];
int len = strlen(receivebuf);
memset(buf, '\0', 10);
memcpy(buf, "ABCDEFGH", 8);
//printf("------- buf = %s, line[%d]\n", buf, __LINE__);
if(rtn == 0)
{
//首先chucksum
if(rtn == 0)
{
//字符串转数字,计算chksum值
memset(chucksum,'\0',4);
for(int i = 0;i<4;i++)
{
chucksum[i] = receivebuf[len-6+i];
}
chucksum[4] = '\0';
strncpy(chucksum,chucksum,4);
for(int i = 1;i<len-6;i++)
{
chkNumber = chkNumber + receivebuf[i];
//printf("receivebuf[i]=%c\n",receivebuf[i]);
}
chkNumber = chkNumber%65536;
chkNumber = ~chkNumber+1+65536;
if(chkNumber != hex2dec(chucksum))
{
//printf("chkNumber=%d\n",chkNumber);
//printf("hex2dec(chucksum)=%d\n",hex2dec(chucksum));
rtn = -1;
}
}
//解析设备类型,填充设备数据,主要解析cid2 length commandinfo chucksum
#if 1
if(rtn == 0)
{
//填充sendbuf
strncpy(sendbuf,receivebuf,7);
memset(cid2,'\0',3);
*(cid2) = *(receivebuf+7);
*(cid2+1) = *(receivebuf+8);
//printf("%s\n",cid2);
if(strcmp(cid2,"41") == 0)
{
//默认数据正常 rtn 赋值00
strncat(sendbuf,"00",2);
memset(length,'\0',5);
//数据为浮点型数据,处理数据长度
for(int i = 0;i<4;i++)
{
length[i] = receivebuf[10+i];
}
strncat(sendbuf,length,4);
length[3] = '\0';
//printf("length = %s\n",length);
datalength = hex2dec(length)/2;
//printf("datalength = %d\n",datalength);
for(int i=0;i<datalength;i++)
{
memset(data,'\0',9);
creatfloatstr(getRandData(0,100),data);
strncat(sendbuf,data,8);
}
//memset(sendbuf,'\0',200);
creatChksum(sendbuf,chucksum);
strncat(sendbuf,chucksum,4);
strncat(sendbuf,"CR",2);
}
if(strcmp(cid2,"42") == 0)
{
//默认数据正常 rtn 赋值00
strncat(sendbuf,"00",2);
//数据为整型数据,处理数据长度
for(int i = 0;i<4;i++)
{
length[3-i] = receivebuf[13+i];
}
strncat(sendbuf,length,4);
length[3] = '0';
datalength = hex2dec(length);
for(int i=0;i<datalength;i++)
{
memset(data,'\0',8);
sprintf(data,"%04X",rand()%101);
strncat(sendbuf,data,8);
}
memset(sendbuf,'\0',4);
creatChksum(sendbuf,chucksum);
strncat(sendbuf,chucksum,4);
strncat(sendbuf,"CR",2);
}
}
#endif
}
//printf("------- buf = %s, line[%d]\n", buf, __LINE__);
return rtn;
}
int main()
{
int rtn = 0;
char *receivebuf = malloc(256*sizeof(char));
strncpy(receivebuf,"~20012441E00201FD3ACR",32*sizeof(char));
char sendbuf[200];
rtn = virtual_data(receivebuf,sendbuf);
//printf("send=%s\n",sendbuf);
exit(0);
return 0;
}
b)编译
gcc -g -o test main.c –lm
c)运行
此种情况看不到程序内部变量是否发生篡改,会直接退出程序,所以不适合用watch。但是又确实发生了栈溢出,在栈保护机制的作用下,程序并不会在执行到导致栈溢出的那行语句时就报错然后终止,而是把子函数全部执行完,再回到main函数中去。但是由于栈已经被破坏,在往main函数跳的时候,就无法获取main函数的栈空间了,所以异常终止。
d)定位
e)总结
即先看堆栈,大致确定是哪个函数。然后进到该函数中,逐条语句执行、查看堆栈信息,直到看到栈被破坏。
遇到函数调用层次比较多时,效率会比较低,但也算有效。