概述
问题描述
如下程序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
]表示增加rsi
和rdi
,然后重复cmps
.只要[rsi]
和[rdi]
比较平等,每次迭代都会设置rflags
寄存器; 这个标准位表明了rsi和rdi谁大
[rsi]
≠[rdi]
的最后一次迭代是根据%rflags寄存器seta
和setb
设置%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编译器测试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复