安全中国首页 > 文章中心 > 安全防护
 
安全中国网友投稿专用上传FTP空间:
Ftp服务器:download.anqn.com
Ftp端口:21
用户名:anqn
密 码:anqn.com
 

4.2.2 突破密码验证程序(图)

更新时间:2008-8-27 0:26:30
责任编辑:池天
热 点:

4.2.2 突破密码验证程序

实验环境要求如表4-2-1所示。
表4-2-1 实验环境

 

推荐使用的环境

   

操作系统

Windows XP sp2

其他Win32操作系统也可进行本实验

编译器

Visual C++ 6.0

如使用其他编译器,需重新调试

编译选项

默认编译选项

VS2003VS2005中的GS编译选项会使栈溢出实验失败

build版本

debug版本

如使用release版本,则需要重新调试

说明:如果完全采用实验指导所推荐的实验环境,将精确地重现指导中所有的细节;否则需要根据具体情况重新调试。

请您在开始实验前务必先确定实验环境是否符合要求。
按照程序的设计思路,只有输入了正确的密码“1234567”之后才能通过验证。程序运行情况如图4.2.2所示。

 
图4.2.2 程序正常运行时的情况
假如我们输入的密码为7个英文字母‘q’,按照字符串的序关系 “qqqqqqq”> “1234567”, strcmp应该返回1,即authenticated为1。OllyDbg动态调试的实际内存情况如图4.2.3所示。
 
图4.2.3 栈帧布局

也就是说,栈帧数据分布情况如表4-2-2所示。
表4-2-2 栈帧数据分布情况

局部变量名

偏移3处的值

偏移2处的值

偏移1处的值

偏移0处的值

buffer[03]

0x0012FB18

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

buffer[47]

0x0012FB1C

NULL

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

authenticated

0x0012FB20

0x00

0x00

0x00

0x01

在观察内存的时候应当注意“内存数据”与“数值数据”的区别。在我们的调试环境中,内存由低到高分布,且计算机体系架构属于传统的“大顶机”( big endian,也有文献称其为“大端机”)。您可以简单地把这种情形理解成Win32系统在内存中由低位向高位存储一个4字节的双字(DWORD),但在作为“数值”应用的时候,却是按照由高位字节向低位字节进行解释。这样一来,在我们的调试环境中,“内存数据”中的DWORD和我们逻辑上使用的“数值数据”是按字节序逆序过的。

例如,变量authenticated在内存中存储为0x 01 00 00 00,这个“内存数据”的双字会被计算机由高位向低位按字节解释成“数值数据” 0x 00 00 00 01。出于便于阅读的目的,OllyDbg在栈区显示的时候已经将内存中双字的字节序反转了,也就是说,栈区栏显示的是“数值数据”,而不是原始的“内存数据”,所以,在栈内看数据时,从左向右对于左边地址的偏移依次为3、2、1、0。请您在实验中注意这一细节。

题外话:与CISIC(复杂指令集)和RISIC(经典指令集)的争论一样,大顶机模式(big endian)和小顶机模式(little endian)的位序问题属于计算机体系结构中不同的实现标准。在Intel x86几乎一统天下的今天,似乎大顶机模式已经成为通用的标准。本着兼容性的原则,在另一些架构中,如ARM体系,用户可以自己选择使用大顶机模式还是小顶机模式。

下面我们试试输入超过7个字符,看看超过buffer[8]边界的数据能不能写进authenticated变量的数据区。为了便于区分溢出的数据,这次我们输入的密码为“qqqqqqqqrst”(‘q’、‘r’、‘s’、‘t’的ASCII码相差1),结果如图4.2.4所示。

 
图4.2.4 覆盖邻接变量

图4.2.4 覆盖邻接变量

栈中的情况和我们分析的一样,从输入的第9个字符开始,将依次写入authenticated变量。按照我们的输入“qqqqqqqqrst”,最终authenticated的值应该是字符‘r’、‘s’、‘t’和用于截断字符串的null所对应的ASCII码0x00747372。
这时的栈帧数据如表4-2-3所示。

表4-2-3 栈帧数据

局部变量名

内存地址

偏移3处的值

偏移2处的值

偏移1处的值

偏移0处的值

buffer

0x0012FB18

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

 

0x0012FB1C

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

authenticated被覆盖前

0x0012FB20

0x00

0x00

0x00

0x01

authenticated被覆盖后

0x0012FB20

NULL

0x74 (‘t’)

0x73 (‘s’)

0x72(‘r’)

authenticated变量的值来源于strcmp函数的返回值,之后会返回给main函数作为密码验证成功与否的标志变量。当authenticated为0时,表示验证成功;反之,验证不成功。

我们已经知道越过数组buffer[8]的边界的后续数据可以改写变量authenticated,那么如果我们用这段溢出数据恰好把authenticated改为0,是不是就可以直接通过验证了呢?

字符串数据最后都有作为结束标志的NULL(0),当我们输入8个‘q’的时候,按照前边的分析,buffer所拥有的8个字节将全部被‘q’的ASCII码0x71填满,而字符串的第9个字符——作为结尾的NULL将刚好写入内存0x0012FB20处,即下一个双字的低位字节,恰好将authenticated从0x 00 00 00 01改成 0x 00 00 00 00,如图4.2.5所示。

 
图4.2.5 修改邻接变量
这时系统栈内的变化过程如表4-2-4所示。
表4-2-4 栈帧数据

局部变量名

内存地址

偏移3处的值

偏移2处的值

偏移1处的值

偏移0处的值

buffer

0x0012FB18

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

 

0x0012FB1C

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

0x71 (‘q’)

authenticated被覆盖前

0x0012FB20

0x00

0x00

0x00

0x01

authenticated被覆盖后

0x0012FB20

0x00

0x00

0x00

0x00 (NULL)

经过上述分析和动态调试,我们知道即使不知道正确的密码“1234567”,只要输入一个为8个字符的字符串,那么字符串中隐藏的第9个截断符NULL就应该能够将authenticated低字节中的1覆盖成0,从而绕过验证程序!修改邻接变量成功的界面如图4.2.6所示。
 
图4.2.6 修改邻接变量成功

题外话:严格说来,并不是任何8个字符的字符串都能冲破上述验证程序。由代码中的authenticated=strcmp(password,PASSWORD),我们知道authenticated的值来源于字符串比较函数strcmp的返回值。按照字符串的序关系,当输入的字符串大于“1234567”时,返回1,这时authenticated在内存中的值为0x00000001,可以用字串的截断符NULL淹没authenticated的低位字节而突破验证;当输入字符串小于“1234567”时(例如,“0123”等字符串),函数返回-1,这时authenticated在内存中的值按照双字-1的补码存放,为0xFFFFFFFF,如果这时也输入8个字符的字符串,截断符淹没authenticated低字节后,其值变为0xFFFFFF00,所以这时是不能冲破验证程序的。图4.2.6所示的“01234567”输入就属于这种情形。如果您感兴趣,可以尝试进一步调试研究这种情况。

 
相关文章
一日一文章
 
一日一软件
一日一动画