StackShield是一个防止缓冲区溢出的工具。主要是通过数组来保存返回地址,避免stack smashing攻击。stack smashing攻击如下图所示。图中展示的是x86下的一个栈帧结构,攻击者利用C语言中不检查数组边界的特点,在往数组局部变量写值的时候,写越界,以特定的值覆盖返回地址,从而达到在程序返回时执行恶意代码的目的。
StackShield的防护思路是,另外开辟一个数组保存返回地址,在函数返回时从数组中恢复返回地址,从而达到保护返回地址的目的,如下图所示。
其程序的主要结构如下所示:
其主要文件及其相关关系如下所示。
addrcallcheck.h
定义一个全局变量shielddatabase;
const char accheader[] =
"#-----------------------------------------\n"
".data\n"
".comm shielddatabase,4,4\n"
"#-----------------------------------------\n";
把该全局变量的值与寄存器相比,如果不同,则报错。
const char acccall[] =
"#-----------------------------------------\n"
" cmpl $shielddatabase,<REGISTER>\n"
" jbe .LSHIELDCALL<CALLCOUNT>\n"
" movl $1,%eax\n"
" movl $-1,%ebx\n"
" int $0x80\n"
".LSHIELDCALL<CALLCOUNT>:\n"
"#-----------------------------------------\n";
retrangecheck.h
定义一个全局变量shielddatabase;
const char rrcheader[] = /* Identical to accheader */
"#-----------------------------------------\n"
".data\n"
".comm shielddatabase,4,4\n"
"#-----------------------------------------\n";
把该全局变量的值与寄存器相比,如果不同,则报错
const char rrcepilog[] =
"#-----------------------------------------\n"
" cmpl $shielddatabase,(%esp)\n"
" jbe .LSHIELDRETRANGE<EPILOGCOUNT>\n"
" movl $1,%eax\n"
" movl $-1,%ebx\n"
" int $0x80\n"
".LSHIELDRETRANGE<EPILOGCOUNT>:\n"
"#-----------------------------------------\n";
globalretstack.h
定义三个全局变量:retptr,指向返回数组的当前指针;rettop,指向返回地址栈顶的指针;retarray,指向返回数组的指针(栈底);
const char grsheader[] =
"#-----------------------------------------\n"
".data\n"
".comm retptr,4,4\n"
".comm rettop,4,4\n"
".comm retarray,<BUFFSIZE>,4\n"
"#-----------------------------------------\n";
如果当前栈指针小于等于栈顶(栈是向下增长),则把当前的返回地址加入到数组中去;
const char grsprolog[] =
"#-----------------------------------------\n"
" pushl %eax\n"
" pushl %edx\n"
" movl retptr,%eax\n"
" cmpl %eax,rettop\n"
" jbe .LSHIELDPROLOG<PROLOGCOUNT>\n"
" movl 8(%esp),%edx\n"
" movl %edx,(%eax)\n"
".LSHIELDPROLOG<PROLOGCOUNT>:\n"
" addl $4,retptr\n"
" popl %edx\n"
" popl %eax\n"
"#-----------------------------------------\n";
如果当前指针小于等于栈顶指针,则把当前指针所指向的值写回栈中;
const char grsepilog[] =
"#-----------------------------------------\n"
" pushl %eax\n"
" pushl %edx\n"
" addl $-4,retptr\n"
" movl retptr,%eax\n"
" cmpl %eax,rettop\n"
" jbe .LSHIELDEPILOG<EPILOGCOUNT>\n"
" movl (%eax),%edx\n"
" movl %edx,8(%esp)\n"
".LSHIELDEPILOG<EPILOGCOUNT>:\n"
" popl %edx\n"
" popl %eax\n"
"#-----------------------------------------\n";
在函数的main入口处,为retptr,rettop赋值;
const char grsmainprolog[] =
"#-----------------------------------------\n"
" movl $retarray,retptr\n"
" movl $retarray,rettop\n"
" addl $<BUFFSIZE>,rettop\n"
"#-----------------------------------------\n";
检查堆栈中的返回地址是否与当前指针指向的返回地址是否一致,判断其是否被修改
const char grsdetectattackepilog[] =
"#-----------------------------------------\n"
" pushl %eax\n"
" pushl %edx\n"
" addl $-4,retptr\n"
" movl retptr,%eax\n"
" cmpl %eax,rettop\n"
" jbe .LSHIELDEPILOG<EPILOGCOUNT>\n"
" movl (%eax),%edx\n"
" cmpl %edx,8(%esp)\n"
" je .LSHIELDEPILOG<EPILOGCOUNT>\n"
" movl $1,%eax\n"
" movl $-1,%ebx\n"
" int $0x80\n"
".LSHIELDEPILOG<EPILOGCOUNT>:\n"
" popl %edx\n"
" popl %eax\n"
"#-----------------------------------------\n";
在向当前的retptr中写入当前堆栈返回值的时候,增加检查是否越界的功能(prolog,前序)
const char grsanticrashprolog[] =
"#-----------------------------------------\n"
" pushl %eax\n"
" pushl %edx\n"
" cmpl $0xFFFFFFFF,retptr\n"
" je .LSHIELDANTICRASHPROLOG<PROLOGCOUNT>\n"
" movl retptr,%eax\n"
" cmpl %eax,rettop\n"
" jbe .LSHIELDPROLOG<PROLOGCOUNT>\n"
" movl 8(%esp),%edx\n"
" movl %edx,(%eax)\n"
".LSHIELDPROLOG<PROLOGCOUNT>:\n"
" addl $4,retptr\n"
".LSHIELDANTICRASHPROLOG<PROLOGCOUNT>:\n"
" popl %edx\n"
" popl %eax\n"
"#-----------------------------------------\n";
在向当前的retptr中写入当前堆栈返回值的时候,增加检查是否越界的功能(epilog)
const char grsanticrashepilog[] =
"#-----------------------------------------\n"
" cmpl $0xFFFFFFFF,retptr\n"
" je .LSHIELDEPILOG<EPILOGCOUNT>\n"
"#-----------------------------------------\n";
程序运行分析
程序的选项主要包括以下几类:
1. -c,anticrash防止指针越界造成崩溃。
2. -d,detectattack,当攻击发生时,也就是数组中保存的返回地址和堆栈指针中的返回地址不一致时,终止程序。如果没有这个选项,则直接使用数组的保存的返回地址,而不去计较是否一致。
3. -f,addrcallcheck
4. –g,globalretstack
5. –r,retrangecheck
6. –e,指定入口函数,从该函数起,启动返回地址检查机制。