汇编角度解释溢出段错误

        在编写代码时,由于读写了系统保护的内存或者访问了给定地址以外的内存地址,这时会发生段错误,直接性的结果就是代码出错退出,这也仅仅是系统唯一能让我们直观的可知这是发生段错误了,但是对代码在系统中的运行情况以及内存情况一无所获,因为高级语言屏蔽了这些底层的实现。但是所有的语言编译的最终可执行代码都是1和0形式的机器代码,机器代码由汇编语言编译而成,所以汇编是低级语言即直接可以操作内存。下面来用汇编来解释段错误:

C代码

int  test_fun1(char * ptr)
{
memcpy(ptr,"1234567890",strlen("1234567890"));
return 0;
}


int  test_fun2()
{
char buf[5];
test_fun1(buf);
return 0;
}

代码test_fun2的汇编形式

mov %esp,%ebp
subl $20,%esp
leal -8(%ebp),%ebx
mov %ebx,(%esp)
call test_fun1
ret

栈结构图:


因为函数test_fun1中向ptr指向的内存写了10个字节的数据,而这块内存大小只有5个字节,所以它会把%ebp的备份覆盖以及test_fun2的函数的返回地址,所以当它返回时会出错的。

段错误不紧影响代码的运行及维护,而且往往会变成黑客入侵的入口,黑客一般分三步来攻击(借助段错误攻击):

1.定位栈
2.放置可执行代码
3.进行溢出操作,用上面可执行代码地址来覆盖程序中的某个地址,以达到程序运行到覆盖的地点时,执行黑客代码

       比如上面那段测试代码,黑客就会找到该函数的栈,然后对ptr指向的内存写入足够覆盖返回地址的数据,一般覆盖返回地址的数据是指向另一端黑客的代码地址,当函数返回时,因为已经覆盖,所以直接跳转到黑客的代码,这时你的机器就被黑客控制了。
       因为黑客是依靠上述三步来进行攻击,那么现在的操作系统也从这三方面进行了安全提升:

1.随机地址
用户代码编译后的栈地址是不定的
2.可执行代码区域进行限制
现在的栈可读可写不能执行
3.系统在代码中放置数据检测是否发生溢出

如test_fun2的添加了检测数据的汇编形式
push    %ebp
movl %esp,%ebp
subl $20,%esp
movl %gs:20,-4(%esp)
leal -12(%ebp),%ebx
mov %ebx,(%esp)
call test_fun1
movl -8(%ebp),%eax
xorl %gs:20,%eax
je      .L1
call    __stack_chk_fail
.L1
ret

栈如图:


      在存放buf地址的后面加存放了%gs:20,当调用完函数test_fun1时,执行xorl %gs:20,%eax,检测存放的值有无改变,如果已经改变了则异常退出,否则正常执行返回。
所以在现代系统中,只要发生了溢出系统都会检测到并异常退出程序。

      虽然现在的系统都对段错误添加了多种手段来检测段错误,预防黑客借助段错误攻击,但是一个良好的编程习惯,条理清晰的逻辑结构是最好的预防段错误的方法,编程时不要边想边写,而是在编码之前把整个需要实现的功能,以功能实现过程的逻辑结构画出来,然后根据这个结构图再编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值