我是靠谱客的博主 任性猎豹,最近开发中收集的这篇文章主要介绍缓冲区保护机制问题描述备注问题分析关键点受保护代码VS编译器测试,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

问题描述

如下程序pwd.c用于判断输入的密码是否正确

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define PASSWORD "1234567"

int verify(char *password)

{

int auth;

char buffer[8];

auth = strcmp(password, PASSWORD);

strcpy(buffer, password);

return auth;

}

int main(void){

int flag = 0;

char pass[1024];

while(1){

printf("enter the password:t");

scanf("%s", pass);

flag = verify(pass);

if(flag)

printf("password incorrect!n");

else{

printf("congratulation!n");

break;

}

}

}

在关闭缓冲区溢出保护后进行编译:(Ubuntu 12.04 32位,gcc版本4.6.3)

gcc -fno-stack-protector -z execstack -o pwd.out pwc.c

在命令行输入

./pwd.out运行,

发现输入:11111111、12344444等字符串时会提示密码不正确,而在输入qqqqqqqq、12355555时会提示密码正确。

请:

(1)分析这些现象产生的原因,并总结规律;

(2)在缺省编译状态下,例如gcc pwd.c -o pwd.out后,运行程序不会产生这样的结果,试从汇编代码这一级别进行解释;

(3)测试其他的c语言编译器(例如windows下的visual studio,gcc的低版本2.7.3等)是否有缓冲区溢出保护功能。

备注

字符串

对于字符串类型,时刻记住最后隐藏了一个NULL(用于判断字符串结束)

一个n长的字符串实际上是存在n+1个字符

对于strcpy会自动添加“”在末尾,对于strncpy要求手动添加

strcmp

  • 从C角度

 

可以看到,如果str1>str2返回1,str1<str2返回-1,str1=str2返回0

  • 从汇编

 80484ab:   89 d6                   mov    %edx,%esi
 80484ad:   89 c7                   mov    %eax,%edi
 80484af:   f3 a6                   repz cmpsb %es:(%edi),%ds:(%esi)
 80484b1:   0f 97 c2                seta   %dl
 80484b4:   0f 92 c0                setb   %al
 80484b7:   89 d1                   mov    %edx,%ecx
 80484b9:   28 c1                   sub    %al,%cl
 80484bb:   89 c8                   mov    %ecx,%eax
 80484bd:   0f be c0                movsbl %al,%eax
 80484c0:   89 45 f4                mov    %eax,-0xc(%ebp)

(esi和edi存有要求比较字符串的首地址)

[cmps ]比较[rsi][rdi]。 [repz ]表示增加rsirdi,然后重复cmps.只要[rsi][rdi]比较平等,每次迭代都会设置rflags寄存器; 这个标准位表明了rsi和rdi谁大

[rsi][rdi]的最后一次迭代是根据%rflags寄存器setasetb设置%dl和%al

  • seta dl 如果设置了上述某个标志位,则将dl设置为1;如果不是,则将dl设置为0

  • setb al 如果设置了上述某个标志位,则将al设置为1,如果不是,则设置为0

返回%dl-%al(1或0或-1)存在%eax中,也就是我们从C发现的现象

缓冲区溢出保护

 80484f2:   65 a1 14 00 00 00       mov    %gs:0x14,%eax
 80484f8:   89 45 f4                mov    %eax,-0xc(%ebp)
 80484fb:   31 c0                   xor    %eax,%eax
 //数组缓存区保护
 
 //爆栈标志设置
 8048524:   8b 45 e4                mov    -0x1c(%ebp),%eax
 8048527:   89 44 24 04             mov    %eax,0x4(%esp)
 804852b:   8d 45 ec                lea    -0x14(%ebp),%eax
 804852e:   89 04 24                mov    %eax,(%esp)
 //存了两个数组,其中-0x1c(%ebp)存的是一个数组password首地址,而-0x14(%ebp)是buffer数组的首地址,大小为8
     
 注意buffer数组后面紧跟%gs:0x14

  8048539:  8b 75 f4                mov    -0xc(%ebp),%esi
 804853c:   65 33 35 14 00 00 00    xor    %gs:0x14,%esi
 8048543:   74 05                   je     804854a <verify+0x66>
 8048545:   e8 86 fe ff ff          call   80483d0 <__stack_chk_fail@plt>
 804854a:   83 c4 30                add    $0x30,%esp
 
 //只有当数组末尾 -0xc(%ebp)的值没有被覆盖时(栈溢出),才不会调用<__stack_chk_fail@plt>

gcc 默认情况下是开启堆栈检查,即 gcc -fstack-protector=strong 可通过 gcc -fno-stack-protector关闭检查。

gs 一般 用来存放线程局部变量,比如 errno...

问题分析

先看汇编--verify

 

1.将password、PASSWORD首地址传到%esi和%edi

2.strcmp比较并将结果传到 -0xc(%esp) 位置,即muth

3.strcpy设置参数,一个是password首地址,一个是-0x14(%ebp),buffer首地址,

4.将password传到从buffer起始的位置中

 

关键点

假设关闭保护

bertify只做了两件事情,strcmp和strcpy

strcmp->muth

比较宏字符串和1024长字符串的大小,设置muth的4个字节值为0x0000000、0x00000001、0xffffffff(是补码!)

strcpy->溢出

我们的异常来自于buffer字符是否溢出到muth上

  • 不溢出(strlen(buffer)<=7)

结果正常

  • 溢出(这里我先假设strlen(buffer)==8,strcpy后我们会溢出一个字节的“”到muth中)

    • 如果buffer<1234567

      • muth等于0xffffffff,溢出的“”覆盖muth变为0x00ffffff,verify返回不为0,password incorrect

    • 如果buffer>1234567

      • muth等于0x00000001,当我们注意整型数据是小端法哦!!

      • muth存为0x01 0x00 0x00 0x00,溢出的“”覆盖muth变为0x00000000,verify返回为0,congratulation!

    • buffer不可能等于12345678,我们的假设前提是溢出,也就是buffer的字符数大于7

受保护代码

gcc pwd.c -o pwd.out

 

这里它非常聪明

  • 在未保护状态下,buffer后面紧跟muth,buffer的溢出影响muth结果

  • 在受保护状态下,原来auth位置放置了一个%gs:0x14的爆栈检测标志,muth放到了buffer下面

    • buffer溢出改变%gs:0x14,导致程序走向__stack_chk_fail@plt

    • 特别注意的是%gs:0x14存的是0x00,所以溢出一个“”是不会异常的,但如果strlen(password)==9,那么还会溢出其他字符导致异常

现象(这里你不用看也没事,只是给图片演示)

改变溢出标志数%gs:0x14为NULL,为什么不会溢出呢??。

 

应该%gs:0x14本身就是0,所以即使是保护模式下,一个字符串实际上可输入的字符数是可以将保护标志%gs:0x14充当NULL

例如 char[n] ,在保护模式在也可以输入n个字符而不影响

在未保护模式下,字符数组末尾没有%gs而是其他的重要数据,所以是n-1个字符

 

显然只有完整的九个字符才会真正覆盖%gs而报错

 

这是完全不能strcmp且strcpy不覆盖的情况

VS编译器测试

最后

以上就是任性猎豹为你收集整理的缓冲区保护机制问题描述备注问题分析关键点受保护代码VS编译器测试的全部内容,希望文章能够帮你解决缓冲区保护机制问题描述备注问题分析关键点受保护代码VS编译器测试所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(32)

评论列表共有 0 条评论

立即
投稿
返回
顶部