前文中我们对数据库安全漏洞中最为常见的一种——缓冲区溢出漏洞进行了简单的概念介绍。在静态数据溢出、栈溢出和堆溢出三种类型中,栈溢出和堆溢出相对来说更复杂,且害处范围较大linux telnetd密钥处理缓冲区溢出漏洞,在前文概念介绍的基础上,本文对windows下的栈结构进行更直观的介绍,通过构造一段代码示例,更清晰的演示栈结构下的缓冲区溢出漏洞原理。
下边我们将构造一段代码,完成3个任务:
1.演示WIN下栈的结构
2.演示缓冲区溢出改变函数控制流程
3.演示缓冲区溢出覆盖返回地址(绑架函数)
下边的程序包含一个主函数main和另外一个子函数re_choose。re_choose函数用于把从main函数中取得的输入字符串和储存的字符串liusicheng做对比。倘若输入的字符串和储存的字符串一致则返回0。若果不一致则可能返回1或则-1。同时还人为制造了一个缓冲区溢出点strcpy(buffer,input)。input有1024的空间,而buffer只有44的空间。只要input超过44则都会引起缓冲区溢出。main函数取re_choose返回值假如返回1或-1走if。假如返回0则走else。将用缓冲区溢出来让返回1或则-1也走else。
#include
#include
#defineture_password"liusicheng"
intre_choose(char*input)
intresult;
charbuffer[44];
result=strcmp(input,ture_password);
strcpy(buffer,input);//缓冲注入点
returnresult;
voidmain()
intchoose=0;
charinput[1024];
scanf("%s",input);
choose=re_choose(input);
if(choose==1||choose==-1)
printf("errorn");
else
printf("turen");
编译出里面代码的release版,倒入IDApro中得到反编译代码。右图是MAIN函数的流程结构。清楚的看见main函数的整个控制流程和main函数的栈从构建到销毁的全过程。栈主要用在函数调用上。进程调用的开始会调用大量系统函数,其中大量函数的地址是固定不变的(只和操作系统版本有关系),这种固定的函数将成为之后用于跳转的平台。本文先不涉及那些函数。直接跳到main函数开始介绍。栈的结构是4个字节为一层。假如超过4个字节。根据4个整数倍储存。不足4个字节根据4个字节储存。栈的主要操作只有2种push和POP。push是把寄存器的内容压入到栈中,pop是把栈中的内容释放掉。ebp是当前栈帧的栈底,esp是当前栈帧的栈顶。(注意因为栈是次序执行的所以同一时间只有一个栈顶和一个栈底。但栈底通常不是整个系统栈的栈底,而只是当前这个栈帧的栈底)。栈的结构采用先进先出,后进后出的原则。所以当创建一个栈的时侯会遵守如下步骤:
1把上一个栈帧的栈底的表针压入当前栈保存上去(pushebp)。这一步当然是2步:第一步压入返回地址linux telnetd密钥处理缓冲区溢出漏洞,第二步压入当上一个栈帧的ebp。
2把上一个栈帧的栈底联通到上一个栈帧的栈顶(movebp,esp)。自此这个栈的栈底就确定且不会发生任何改变。栈顶esp会仍然发生变化。
3接着分配局部函数(subesp,404h)。本程序中2个变量1个是4字节1个是1024字节。加一起刚好是0x404个字节。须要栈顶上移0x404。注意栈的方向和显存相反。数据步入显存是从低地址向高地址写,而栈则是从高地址向低地址写。正是这些结构,给了后来数据改写之前数据的机会。栈顶的值会随着栈中数据随时进行调整。
注意:上图中var_404=-404h、str1=-400h
同样栈撤消的时侯基本可以根据栈构建的逆操作进行。首先把栈底值覆盖栈顶(movesp,ebp)。接着栈中弹出当前ebp的值(popebp)。之后跳转EIP中储存的上个函数的返回地址(retn),回到前一个栈帧中(上一个函数中)删掉返回地址行(addesp4)。到此栈被完全撤消。至此一个栈从构建到撤消的全部过程早已完成。我们不仅关心一个栈的创建和衰落,更关心的就是栈是怎样传递返回值和参数的。右图是re_choose的反汇编图:清楚的解释了,在栈中是怎么传参和返回值的。
注上图中var_4=-4、str1=8
main函数从callsub_401000这句开始,创建子函数re_choose的栈帧,开始也是和main一样的栈创建过程。直至执行到moveax,[ebp+str1],这句就是你们最关心的传参。在栈中固定不动的是栈底(ebp)。借助栈底为座标向低位显存联通8个字节取值。取到存在main中的input。装入eax寄存器中传入re_choose用于估算。同样的机制看后半段从re_choose中(moveax,[ebp+var_4])取栈底向低地址偏斜4个字节的内容。存在eax中,main把eax值存入ebp-404(mov[ebp+var_404],eax)这个地址中用于后续的判别。至此栈的基本结构基本操作早已介绍完毕。栈缓冲区溢出的症结和栈的自身结构密切相关。正是因为栈中数据是先存入的在显存高地址,后入的在显存低地址。所以给了后入的机会,一旦超过栈起初分配的宽度则会直接覆盖先前存在显存高地址中的数据或指令。因而带来不可预知的结果。
至于函数的参数传入的次序是从左到右还是从右到左(局部变量inta,b是先压a还是先压b),函数返回时恢复栈平衡是让母函数作还是子函数作。这部份和函数调用约定相关,主要的调用约定分为,_cdecl、_fastcall和_stdcall。通常VS默认采用_stdcall和windowsapi保持一致。stdcall规则要求:参数从右向左压。(inta,b先压b)。函数退出的时侯自己清除栈中的参数。(图中常常会听到一个参数前面没用直接被addesp4了)
至此,通过前面缓冲区溢出漏洞的实例演示linux 内核,你应当早已了解了栈溢出的漏洞原理。后续,我们将基于此实例,向你们介绍借助栈溢出漏洞的功击形式,以及急剧带来的数据库安全风险。通过这样的实例演示linux安装,让你们对这种漏洞原理形成深刻理解,并梳理出防护思路。